From 298975a98242ca66a0689175e9c8f7b43e9409c5 Mon Sep 17 00:00:00 2001 From: xEricL <37921711+xEricL@users.noreply.github.com> Date: Thu, 17 Oct 2024 02:29:03 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5Feature:=20Add=20support=20for=20Tr?= =?UTF-8?q?ustProxy=20(#3170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ๐Ÿ”ฅ Feature: Add `TrustProxyConfig` and rename `EnableTrustedProxyCheck` to `TrustProxy` * ๐Ÿ“š Doc: Document TrustProxyConfig usage and migration * ๐Ÿšจ Test: Validate and Benchmark use of TrustProxyConfig * ๐Ÿฉน Fix: typo in RequestMethods docstring * ๐Ÿฉน Fix: typos in TrustProxy docstring and JSON tags * ๐Ÿฉน Fix: Move `TrustProxyConfig.Loopback` to beginning of if-statement * ๐ŸŽจ Style: Cleanup spacing for Test_Ctx_IsProxyTrusted * ๐Ÿ“š Doc: Replace `whitelist` with `allowlist` for clarity * ๐Ÿ“š Doc: Improve `TrustProxy` doc wording * ๐Ÿฉน Fix: validate IP addresses in `App.handleTrustedProxy` * ๐Ÿฉน Fix: grammatical errors and capitalize "TLS" --- .github/README.md | 6 +- app.go | 73 +++-- ctx.go | 24 +- ctx_interface_gen.go | 10 +- ctx_test.go | 416 ++++++++++++++++++++----- docs/api/ctx.md | 15 +- docs/api/fiber.md | 78 ++--- docs/middleware/cors.md | 6 +- docs/middleware/earlydata.md | 2 +- docs/whats_new.md | 31 ++ middleware/cors/config.go | 2 +- middleware/earlydata/earlydata_test.go | 14 +- 12 files changed, 510 insertions(+), 167 deletions(-) diff --git a/.github/README.md b/.github/README.md index 68fbd9c9bf..61159412e8 100644 --- a/.github/README.md +++ b/.github/README.md @@ -561,8 +561,10 @@ import ( func main() { app := fiber.New(fiber.Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"0.0.0.0", "1.1.1.1/30"}, // IP address or IP address range + TrustProxy: true, + TrustProxyConfig: fiber.TrustProxyConfig{ + Proxies: []string{"0.0.0.0", "1.1.1.1/30"}, // IP address or IP address range + }, ProxyHeader: fiber.HeaderXForwardedFor, }) diff --git a/app.go b/app.go index e0240d3c16..38a3d17319 100644 --- a/app.go +++ b/app.go @@ -330,29 +330,31 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // 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 + // If you are behind a proxy, you should enable TrustProxy to prevent header spoofing. + // If you enable TrustProxy and do not provide a TrustProxyConfig, Fiber will skip // all headers that could be spoofed. - // If request ip in TrustedProxies whitelist then: + // If the request IP is in the TrustProxyConfig.Proxies allowlist, then: // 1. c.Scheme() 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.Host() and c.Hostname() get value from X-Forwarded-Host header - // But if request ip NOT in Trusted Proxies whitelist then: - // 1. c.Scheme() 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 + // But if the request IP is NOT in the TrustProxyConfig.Proxies allowlist, then: + // 1. c.Scheme() WON'T get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, + // will return https when a TLS connection is handled by the app, or http otherwise. // 2. c.IP() WON'T get value from ProxyHeader header, will return RemoteIP() from fasthttp context // 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. // + // To automatically trust all loopback, link-local, or private IP addresses, + // without manually adding them to the TrustProxyConfig.Proxies allowlist, + // you can set TrustProxyConfig.Loopback, TrustProxyConfig.LinkLocal, or TrustProxyConfig.Private to true. + // // Default: false - EnableTrustedProxyCheck bool `json:"enable_trusted_proxy_check"` + TrustProxy bool `json:"trust_proxy"` - // Read EnableTrustedProxyCheck doc. + // Read TrustProxy doc. // - // Default: []string - TrustedProxies []string `json:"trusted_proxies"` - trustedProxiesMap map[string]struct{} - trustedProxyRanges []*net.IPNet + // Default: DefaultTrustProxyConfig + TrustProxyConfig TrustProxyConfig `json:"trust_proxy_config"` // 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 @@ -372,7 +374,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // Default: nil StructValidator StructValidator - // RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish. + // RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. // // Optional. Default: DefaultMethods RequestMethods []string @@ -385,6 +387,36 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa EnableSplittingOnParsers bool `json:"enable_splitting_on_parsers"` } +// Default TrustProxyConfig +var DefaultTrustProxyConfig = TrustProxyConfig{} + +// TrustProxyConfig is a struct for configuring trusted proxies if Config.TrustProxy is true. +type TrustProxyConfig struct { + ips map[string]struct{} + + // Proxies is a list of trusted proxy IP addresses or CIDR ranges. + // + // Default: []string + Proxies []string `json:"proxies"` + + ranges []*net.IPNet + + // LinkLocal enables trusting all link-local IP ranges (e.g., 169.254.0.0/16, fe80::/10). + // + // Default: false + LinkLocal bool `json:"link_local"` + + // Loopback enables trusting all loopback IP ranges (e.g., 127.0.0.0/8, ::1/128). + // + // Default: false + Loopback bool `json:"loopback"` + + // Private enables trusting all private IP ranges (e.g., 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7). + // + // Default: false + Private bool `json:"private"` +} + // RouteMessage is some message need to be print when server starts type RouteMessage struct { name string @@ -510,8 +542,8 @@ func New(config ...Config) *App { app.config.RequestMethods = DefaultMethods } - app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies)) - for _, ipAddress := range app.config.TrustedProxies { + app.config.TrustProxyConfig.ips = make(map[string]struct{}, len(app.config.TrustProxyConfig.Proxies)) + for _, ipAddress := range app.config.TrustProxyConfig.Proxies { app.handleTrustedProxy(ipAddress) } @@ -529,17 +561,22 @@ func New(config ...Config) *App { return app } -// Adds an ip address to trustedProxyRanges or trustedProxiesMap based on whether it is an IP range or not +// Adds an ip address to TrustProxyConfig.ranges or TrustProxyConfig.ips based on whether it is an IP range or not func (app *App) handleTrustedProxy(ipAddress string) { if strings.Contains(ipAddress, "/") { _, ipNet, err := net.ParseCIDR(ipAddress) if err != nil { log.Warnf("IP range %q could not be parsed: %v", ipAddress, err) } else { - app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet) + app.config.TrustProxyConfig.ranges = append(app.config.TrustProxyConfig.ranges, ipNet) } } else { - app.config.trustedProxiesMap[ipAddress] = struct{}{} + ip := net.ParseIP(ipAddress) + if ip == nil { + log.Warnf("IP address %q could not be parsed", ipAddress) + } else { + app.config.TrustProxyConfig.ips[ipAddress] = struct{}{} + } } } diff --git a/ctx.go b/ctx.go index 607a678ff8..9e61d0903e 100644 --- a/ctx.go +++ b/ctx.go @@ -155,7 +155,7 @@ type TLSHandler struct { // GetClientInfo Callback function to set ClientHelloInfo // Must comply with the method structure of https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/tls/common.go;l=554-563 -// Since we overlay the method of the tls config in the listener method +// Since we overlay the method of the TLS config in the listener method func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) { t.clientHelloInfo = info return nil, nil //nolint:nilnil // Not returning anything useful here is probably fine @@ -684,7 +684,7 @@ func (c *DefaultCtx) GetReqHeaders() map[string][]string { // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. // Example: URL: https://example.com:8080 -> Host: example.com:8080 // 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. +// Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) Host() string { if c.IsProxyTrusted() { if host := c.Get(HeaderXForwardedHost); len(host) > 0 { @@ -702,7 +702,7 @@ func (c *DefaultCtx) Host() string { // Returned value is only valid within the handler. Do not store any references. // Example: URL: https://example.com:8080 -> Hostname: example.com // 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. +// Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) Hostname() string { addr, _ := parseAddr(c.Host()) @@ -720,7 +720,7 @@ func (c *DefaultCtx) Port() string { // IP returns the remote IP address of the request. // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. -// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) IP() string { if c.IsProxyTrusted() && len(c.app.config.ProxyHeader) > 0 { return c.extractIPFromHeader(c.app.config.ProxyHeader) @@ -1116,7 +1116,7 @@ func (c *DefaultCtx) Path(override ...string) string { } // Scheme contains the request protocol string: http or https for TLS requests. -// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) Scheme() string { if c.fasthttp.IsTLS() { return schemeHTTPS @@ -1819,20 +1819,26 @@ func (c *DefaultCtx) configDependentPaths() { } // IsProxyTrusted checks trustworthiness of remote ip. -// If EnableTrustedProxyCheck false, it returns true +// If Config.TrustProxy false, it returns true // IsProxyTrusted can check remote ip by proxy ranges and ip map. func (c *DefaultCtx) IsProxyTrusted() bool { - if !c.app.config.EnableTrustedProxyCheck { + if !c.app.config.TrustProxy { return true } ip := c.fasthttp.RemoteIP() - if _, trusted := c.app.config.trustedProxiesMap[ip.String()]; trusted { + if (c.app.config.TrustProxyConfig.Loopback && ip.IsLoopback()) || + (c.app.config.TrustProxyConfig.Private && ip.IsPrivate()) || + (c.app.config.TrustProxyConfig.LinkLocal && ip.IsLinkLocalUnicast()) { return true } - for _, ipNet := range c.app.config.trustedProxyRanges { + if _, trusted := c.app.config.TrustProxyConfig.ips[ip.String()]; trusted { + return true + } + + for _, ipNet := range c.app.config.TrustProxyConfig.ranges { if ipNet.Contains(ip) { return true } diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index 62f2d368ad..aa317ecb00 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -127,19 +127,19 @@ type Ctx interface { // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. // Example: URL: https://example.com:8080 -> Host: example.com:8080 // 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. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. Host() string // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. // Returned value is only valid within the handler. Do not store any references. // Example: URL: https://example.com:8080 -> Hostname: example.com // 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. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. Hostname() string // Port returns the remote port of the request. Port() string // IP returns the remote IP address of the request. // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. - // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. IP() string // extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. // When IP validation is enabled, any invalid IPs will be omitted. @@ -209,7 +209,7 @@ type Ctx interface { // Optionally, you could override the path. Path(override ...string) string // Scheme contains the request protocol string: http or https for TLS requests. - // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. Scheme() string // Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. Protocol() string @@ -315,7 +315,7 @@ type Ctx interface { // here the features for caseSensitive, decoded paths, strict paths are evaluated configDependentPaths() // IsProxyTrusted checks trustworthiness of remote ip. - // If EnableTrustedProxyCheck false, it returns true + // If Config.TrustProxy false, it returns true // IsProxyTrusted can check remote ip by proxy ranges and ip map. IsProxyTrusted() bool // IsFromLocal will return true if request came from local. diff --git a/ctx_test.go b/ctx_test.go index a94e4cb42b..eef29a97ad 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -1572,7 +1572,7 @@ func Test_Ctx_Host_UntrustedProxy(t *testing.T) { t.Parallel() // Don't trust any proxy { - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{}}) + app := New(Config{TrustProxy: true, TrustProxyConfig: TrustProxyConfig{Proxies: []string{}}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") @@ -1581,7 +1581,7 @@ func Test_Ctx_Host_UntrustedProxy(t *testing.T) { } // Trust to specific proxy list { - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}}) + app := New(Config{TrustProxy: true, TrustProxyConfig: TrustProxyConfig{Proxies: []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") @@ -1594,7 +1594,7 @@ func Test_Ctx_Host_UntrustedProxy(t *testing.T) { func Test_Ctx_Host_TrustedProxy(t *testing.T) { t.Parallel() { - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) + app := New(Config{TrustProxy: true, TrustProxyConfig: TrustProxyConfig{Proxies: []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") @@ -1607,7 +1607,7 @@ func Test_Ctx_Host_TrustedProxy(t *testing.T) { func Test_Ctx_Host_TrustedProxyRange(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) + app := New(Config{TrustProxy: true, TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.0.0.0/30"}}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") @@ -1619,7 +1619,7 @@ func Test_Ctx_Host_TrustedProxyRange(t *testing.T) { func Test_Ctx_Host_UntrustedProxyRange(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.0.0.0/30"}}) + app := New(Config{TrustProxy: true, TrustProxyConfig: TrustProxyConfig{Proxies: []string{"1.0.0.0/30"}}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") @@ -1653,7 +1653,7 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { } { app := New(Config{ - EnableTrustedProxyCheck: false, + TrustProxy: false, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) require.True(t, c.IsProxyTrusted()) @@ -1661,26 +1661,26 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { { app := New(Config{ - EnableTrustedProxyCheck: true, + TrustProxy: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) require.False(t, c.IsProxyTrusted()) } { app := New(Config{ - EnableTrustedProxyCheck: true, - - TrustedProxies: []string{}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{}, + }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) require.False(t, c.IsProxyTrusted()) } { app := New(Config{ - EnableTrustedProxyCheck: true, - - TrustedProxies: []string{ - "127.0.0.1", + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"127.0.0.1"}, }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -1688,10 +1688,9 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { } { app := New(Config{ - EnableTrustedProxyCheck: true, - - TrustedProxies: []string{ - "127.0.0.1/8", + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"127.0.0.1/8"}, }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -1699,10 +1698,9 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { } { app := New(Config{ - EnableTrustedProxyCheck: true, - - TrustedProxies: []string{ - "0.0.0.0", + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.0"}, }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -1710,10 +1708,9 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { } { app := New(Config{ - EnableTrustedProxyCheck: true, - - TrustedProxies: []string{ - "0.0.0.1/31", + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.1/31"}, }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -1721,10 +1718,39 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { } { app := New(Config{ - EnableTrustedProxyCheck: true, - - TrustedProxies: []string{ - "0.0.0.1/31junk", + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.1/31junk"}, + }, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Private: true, + }, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Loopback: true, + }, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } + { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + LinkLocal: true, }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -1758,7 +1784,10 @@ func Benchmark_Ctx_Hostname(b *testing.B) { } // Trust to specific proxy list { - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []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") @@ -1771,7 +1800,10 @@ func Benchmark_Ctx_Hostname(b *testing.B) { 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"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []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") @@ -1784,7 +1816,10 @@ func Test_Ctx_Hostname_TrustedProxy(t *testing.T) { func Test_Ctx_Hostname_TrustedProxy_Multiple(t *testing.T) { t.Parallel() { - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []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, google2.com") @@ -1797,7 +1832,10 @@ func Test_Ctx_Hostname_TrustedProxy_Multiple(t *testing.T) { func Test_Ctx_Hostname_TrustedProxyRange(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.0.0.0/30"}}, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") @@ -1809,7 +1847,10 @@ func Test_Ctx_Hostname_TrustedProxyRange(t *testing.T) { func Test_Ctx_Hostname_UntrustedProxyRange(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.0.0.0/30"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"1.0.0.0/30"}}, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") c.Request().Header.Set(HeaderXForwardedHost, "google1.com") @@ -1927,7 +1968,11 @@ func Test_Ctx_IP_ProxyHeader_With_IP_Validation(t *testing.T) { // 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}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.8.0.1"}}, + ProxyHeader: HeaderXForwardedFor, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") require.Equal(t, "0.0.0.0", c.IP()) @@ -1936,7 +1981,11 @@ func Test_Ctx_IP_UntrustedProxy(t *testing.T) { // 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}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.0.0.0"}}, + ProxyHeader: HeaderXForwardedFor, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") require.Equal(t, "0.0.0.1", c.IP()) @@ -2613,7 +2662,7 @@ func Benchmark_Ctx_Scheme(b *testing.B) { // go test -run Test_Ctx_Scheme_TrustedProxy func Test_Ctx_Scheme_TrustedProxy(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}}) + app := New(Config{TrustProxy: true, TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.0.0.0"}}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) @@ -2638,7 +2687,10 @@ func Test_Ctx_Scheme_TrustedProxy(t *testing.T) { // go test -run Test_Ctx_Scheme_TrustedProxyRange func Test_Ctx_Scheme_TrustedProxyRange(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.0.0.0/30"}}, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) @@ -2663,7 +2715,10 @@ func Test_Ctx_Scheme_TrustedProxyRange(t *testing.T) { // go test -run Test_Ctx_Scheme_UntrustedProxyRange func Test_Ctx_Scheme_UntrustedProxyRange(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.1.1.1/30"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"1.1.1.1/30"}}, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) @@ -2688,7 +2743,10 @@ func Test_Ctx_Scheme_UntrustedProxyRange(t *testing.T) { // go test -run Test_Ctx_Scheme_UnTrustedProxy func Test_Ctx_Scheme_UnTrustedProxy(t *testing.T) { t.Parallel() - app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}}) + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{Proxies: []string{"0.8.0.1"}}, + }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) @@ -6173,7 +6231,7 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check simple b.Run("WithProxyCheckSimple", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, + TrustProxy: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") @@ -6189,7 +6247,7 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check simple in parallel b.Run("WithProxyCheckSimpleParallel", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, + TrustProxy: true, }) b.ReportAllocs() b.ResetTimer() @@ -6207,8 +6265,10 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check b.Run("WithProxyCheck", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"0.0.0.0"}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.0"}, + }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") @@ -6224,8 +6284,198 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check in parallel b.Run("WithProxyCheckParallel", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"0.0.0.0"}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.0"}, + }, + }) + 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) + }) + }) + + // Scenario with trusted proxy check allow private + b.Run("WithProxyCheckAllowPrivate", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Private: 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 allow private in parallel + b.Run("WithProxyCheckAllowPrivateParallel", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Private: 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) + }) + }) + + // Scenario with trusted proxy check allow private as subnets + b.Run("WithProxyCheckAllowPrivateAsSubnets", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fc00::/7"}, + }, + }) + 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 allow private as subnets in parallel + b.Run("WithProxyCheckAllowPrivateAsSubnetsParallel", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fc00::/7"}, + }, + }) + 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) + }) + }) + + // Scenario with trusted proxy check allow private, loopback, and link-local + b.Run("WithProxyCheckAllowAll", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Private: true, + Loopback: true, + LinkLocal: 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 allow private, loopback, and link-local in parallel + b.Run("WithProxyCheckAllowAllParallel", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Private: true, + Loopback: true, + LinkLocal: 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) + }) + }) + + // Scenario with trusted proxy check allow private, loopback, and link-local as subnets + b.Run("WithProxyCheckAllowAllowAllAsSubnets", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{ + // Link-local + "169.254.0.0/16", + "fe80::/10", + // Loopback + "127.0.0.0/8", + "::1/128", + // Private + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "fc00::/7", + }, + }, + }) + 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 allow private, loopback, and link-local as subnets in parallel + b.Run("WithProxyCheckAllowAllowAllAsSubnetsParallel", func(b *testing.B) { + app := New(Config{ + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{ + // Link-local + "169.254.0.0/16", + "fe80::/10", + // Loopback + "127.0.0.0/8", + "::1/128", + // Private + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "fc00::/7", + }, + }, }) b.ReportAllocs() b.ResetTimer() @@ -6243,8 +6493,10 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check with subnet b.Run("WithProxyCheckSubnet", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"0.0.0.0/8"}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.0/8"}, + }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") @@ -6260,8 +6512,10 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check with subnet in parallel b.Run("WithProxyCheckParallelSubnet", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"0.0.0.0/8"}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"0.0.0.0/8"}, + }, }) b.ReportAllocs() b.ResetTimer() @@ -6279,8 +6533,10 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check with multiple subnet b.Run("WithProxyCheckMultipleSubnet", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"192.168.0.0/24", "10.0.0.0/16", "0.0.0.0/8"}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"192.168.0.0/24", "10.0.0.0/16", "0.0.0.0/8"}, + }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetRequestURI("http://google.com/test") @@ -6296,8 +6552,10 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check with multiple subnet in parallel b.Run("WithProxyCheckParallelMultipleSubnet", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{"192.168.0.0/24", "10.0.0.0/16", "0.0.0.0/8"}, + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{"192.168.0.0/24", "10.0.0.0/16", "0.0.0.0/8"}, + }, }) b.ReportAllocs() b.ResetTimer() @@ -6315,17 +6573,19 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check with all subnets b.Run("WithProxyCheckAllSubnets", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{ - "127.0.0.0/8", // Loopback addresses - "169.254.0.0/16", // Link-Local addresses - "fe80::/10", // Link-Local addresses - "192.168.0.0/16", // Private Network addresses - "172.16.0.0/12", // Private Network addresses - "10.0.0.0/8", // Private Network addresses - "fc00::/7", // Unique Local addresses - "173.245.48.0/20", // My custom range - "0.0.0.0/8", // All IPv4 addresses + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{ + "127.0.0.0/8", // Loopback addresses + "169.254.0.0/16", // Link-Local addresses + "fe80::/10", // Link-Local addresses + "192.168.0.0/16", // Private Network addresses + "172.16.0.0/12", // Private Network addresses + "10.0.0.0/8", // Private Network addresses + "fc00::/7", // Unique Local addresses + "173.245.48.0/20", // My custom range + "0.0.0.0/8", // All IPv4 addresses + }, }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) @@ -6342,17 +6602,19 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario with trusted proxy check with all subnets in parallel b.Run("WithProxyCheckParallelAllSubnets", func(b *testing.B) { app := New(Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{ - "127.0.0.0/8", // Loopback addresses - "169.254.0.0/16", // Link-Local addresses - "fe80::/10", // Link-Local addresses - "192.168.0.0/16", // Private Network addresses - "172.16.0.0/12", // Private Network addresses - "10.0.0.0/8", // Private Network addresses - "fc00::/7", // Unique Local addresses - "173.245.48.0/20", // My custom range - "0.0.0.0/8", // All IPv4 addresses + TrustProxy: true, + TrustProxyConfig: TrustProxyConfig{ + Proxies: []string{ + "127.0.0.0/8", // Loopback addresses + "169.254.0.0/16", // Link-Local addresses + "fe80::/10", // Link-Local addresses + "192.168.0.0/16", // Private Network addresses + "172.16.0.0/12", // Private Network addresses + "10.0.0.0/8", // Private Network addresses + "fc00::/7", // Unique Local addresses + "173.245.48.0/20", // My custom range + "0.0.0.0/8", // All IPv4 addresses + }, }, }) b.ReportAllocs() diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 00c2422cd9..b771d37eab 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -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 [`TrustProxy`](fiber.md#trustproxy) false, it returns true IsProxyTrusted can check remote ip by proxy ranges and ip map. ```go title="Signature" @@ -841,10 +841,13 @@ func (c Ctx) IsProxyTrusted() bool ```go title="Example" app := fiber.New(fiber.Config{ - // EnableTrustedProxyCheck enables the trusted proxy check - EnableTrustedProxyCheck: true, - // TrustedProxies is a list of trusted proxy IP addresses - TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}, + // TrustProxy enables the trusted proxy check + TrustProxy: true, + // TrustProxyConfig allows for configuring trusted proxies. + // Proxies is a list of trusted proxy IP ranges/addresses + TrustProxyConfig: fiber.TrustProxyConfig{ + Proxies: []string{"0.8.0.0", "0.8.0.1"}, + } }) @@ -1640,7 +1643,7 @@ app.Post("/", func(c fiber.Ctx) error { Contains the request protocol string: http or https for TLS requests. :::info -Please use [`Config.EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) to prevent header spoofing, in case when your app is behind the proxy. +Please use [`Config.TrustProxy`](fiber.md#trustproxy) to prevent header spoofing, in case when your app is behind the proxy. ::: ```go title="Signature" diff --git a/docs/api/fiber.md b/docs/api/fiber.md index 16f23b9e61..6892225e11 100644 --- a/docs/api/fiber.md +++ b/docs/api/fiber.md @@ -42,45 +42,45 @@ app := fiber.New(fiber.Config{ #### Config fields -| Property | Type | Description | Default | -|---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| -| AppName | `string` | This allows to setup app name for the app | `""` | -| 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"}` | -| 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` | -| DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | -| DisableKeepalive | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | -| DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | -| EnableIPValidation | `bool` | 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 value that may be a comma separated string.

**WARNING:** There is a small performance cost to doing this validation. Keep disabled if speed is your only concern and your application is behind a trusted proxy that already validates this header. | `false` | -| EnableSplittingOnParsers | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.

For example, you can use it to parse multiple values from a query parameter like this: `/api?foo=bar,baz == foo[]=bar&foo[]=baz` | `false` | -| EnableTrustedProxyCheck | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustedProxies list.

By default `c.Protocol()` will get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, `c.IP()` will get value from `ProxyHeader` header, `c.Hostname()` will get value from X-Forwarded-Host header.
If `EnableTrustedProxyCheck` is true, and `RemoteIP` is in the list of `TrustedProxies` `c.Protocol()`, `c.IP()`, and `c.Hostname()` will have the same behaviour when `EnableTrustedProxyCheck` disabled, if `RemoteIP` isn't in the list, `c.Protocol()` will return https in case when tls connection is handled by the app, or http otherwise, `c.IP()` will return RemoteIP() from fasthttp context, `c.Hostname()` will return `fasthttp.Request.URI().Host()` | `false` | -| ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. Mounted fiber error handlers are retained by the top-level app and applied on prefix associated requests. | `DefaultErrorHandler` | -| GETOnly | `bool` | Rejects all non-GET requests if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. | `false` | -| IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `nil` | -| Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | -| JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | -| JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | -| PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | -| ProxyHeader | `string` | This will enable `c.IP()` to return the value of the given header key. By default `c.IP()`will return the Remote IP from the TCP connection, this property can be useful if you are behind a load balancer e.g. _X-Forwarded-\*_. | `""` | -| ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). | `4096` | -| ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | -| ReduceMemoryUsage | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | -| RequestMethods | `[]string` | RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | -| ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | -| StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | -| 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` | -| 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. | `""` | -| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | `4096` | -| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | -| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | +| Property | Type | Description | Default | +|---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| AppName | `string` | This allows to setup app name for the app | `""` | +| 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"}` | +| 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` | +| DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | +| DisableKeepalive | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | +| DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | +| EnableIPValidation | `bool` | 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 value that may be a comma separated string.

**WARNING:** There is a small performance cost to doing this validation. Keep disabled if speed is your only concern and your application is behind a trusted proxy that already validates this header. | `false` | +| EnableSplittingOnParsers | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.

For example, you can use it to parse multiple values from a query parameter like this: `/api?foo=bar,baz == foo[]=bar&foo[]=baz` | `false` | +| TrustProxy | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustProxyConfig.Proxies list.

By default `c.Protocol()` will get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, `c.IP()` will get value from `ProxyHeader` header, `c.Hostname()` will get value from X-Forwarded-Host header.
If `TrustProxy` is true, and `RemoteIP` is in the list of `TrustProxyConfig.Proxies` `c.Protocol()`, `c.IP()`, and `c.Hostname()` will have the same behaviour when `TrustProxy` disabled, if `RemoteIP` isn't in the list, `c.Protocol()` will return https when a TLS connection is handled by the app, or http otherwise, `c.IP()` will return RemoteIP() from fasthttp context, `c.Hostname()` will return `fasthttp.Request.URI().Host()` | `false` | +| ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. Mounted fiber error handlers are retained by the top-level app and applied on prefix associated requests. | `DefaultErrorHandler` | +| GETOnly | `bool` | Rejects all non-GET requests if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. | `false` | +| IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `nil` | +| Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | +| JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | +| JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | +| PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | +| ProxyHeader | `string` | This will enable `c.IP()` to return the value of the given header key. By default `c.IP()`will return the Remote IP from the TCP connection, this property can be useful if you are behind a load balancer e.g. _X-Forwarded-\*_. | `""` | +| ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). | `4096` | +| ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | +| ReduceMemoryUsage | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | +| RequestMethods | `[]string` | RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | +| ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | +| StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | +| 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` | +| TrustProxyConfig | `TrustProxyConfig` | Configure trusted proxy IP's. Look at `TrustProxy` doc.

`TrustProxyConfig.Proxies` can take IP or IP range addresses. | `nil` | +| 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. | `""` | +| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | `4096` | +| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | +| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | ## Server listening diff --git a/docs/middleware/cors.md b/docs/middleware/cors.md index 6c6d31cd80..1c5124a992 100644 --- a/docs/middleware/cors.md +++ b/docs/middleware/cors.md @@ -118,7 +118,7 @@ panic: [CORS] Configuration error: When 'AllowCredentials' is set to true, 'Allo | AllowOrigins | `[]string` | AllowOrigins defines a list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. If the special wildcard `"*"` is present in the list, all origins will be allowed. | `["*"]` | | AllowOriginsFunc | `func(origin string) bool` | `AllowOriginsFunc` is a function that dynamically determines whether to allow a request based on its origin. If this function returns `true`, the 'Access-Control-Allow-Origin' response header will be set to the request's 'origin' header. This function is only used if the request's origin doesn't match any origin in `AllowOrigins`. | `nil` | | AllowPrivateNetwork | `bool` | Indicates whether the `Access-Control-Allow-Private-Network` response header should be set to `true`, allowing requests from private networks. This aligns with modern security practices for web applications interacting with private networks. | `false` | -| ExposeHeaders | `string` | ExposeHeaders defines whitelist headers that clients are allowed to access. | `[]` | +| ExposeHeaders | `string` | ExposeHeaders defines an allowlist of headers that clients are allowed to access. | `[]` | | MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, the Access-Control-Max-Age header will not be added and the browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header to 0. | `0` | | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | @@ -191,7 +191,7 @@ The `AllowHeaders` option specifies which headers are allowed in the actual requ The `AllowCredentials` option indicates whether the response to the request can be exposed when the credentials flag is true. If `AllowCredentials` is set to `true`, the middleware adds the header `Access-Control-Allow-Credentials: true` to the response. To prevent security vulnerabilities, `AllowCredentials` cannot be set to `true` if `AllowOrigins` is set to a wildcard (`*`). -The `ExposeHeaders` option defines a whitelist of headers that clients are allowed to access. If `ExposeHeaders` is set to `"X-Custom-Header"`, the middleware adds the header `Access-Control-Expose-Headers: X-Custom-Header` to the response. +The `ExposeHeaders` option defines an allowlist of headers that clients are allowed to access. If `ExposeHeaders` is set to `"X-Custom-Header"`, the middleware adds the header `Access-Control-Expose-Headers: X-Custom-Header` to the response. The `MaxAge` option indicates how long the results of a preflight request can be cached. If `MaxAge` is set to `3600`, the middleware adds the header `Access-Control-Max-Age: 3600` to the response. @@ -207,7 +207,7 @@ When configuring CORS, misconfiguration can potentially expose your application - **Use Credentials Carefully**: If your application needs to support credentials in cross-origin requests, ensure `AllowCredentials` is set to `true` and specify exact origins in `AllowOrigins`. Do not use a wildcard origin in this case. -- **Limit Exposed Headers**: Only whitelist headers that are necessary for the client-side application by setting `ExposeHeaders` appropriately. This minimizes the risk of exposing sensitive information. +- **Limit Exposed Headers**: Only allowlist headers that are necessary for the client-side application by setting `ExposeHeaders` appropriately. This minimizes the risk of exposing sensitive information. ### Common Pitfalls diff --git a/docs/middleware/earlydata.md b/docs/middleware/earlydata.md index b0e39b2f2f..287b22e031 100644 --- a/docs/middleware/earlydata.md +++ b/docs/middleware/earlydata.md @@ -7,7 +7,7 @@ id: earlydata The Early Data middleware for [Fiber](https://github.com/gofiber/fiber) adds support for TLS 1.3's early data ("0-RTT") feature. Citing [RFC 8446](https://datatracker.ietf.org/doc/html/rfc8446#section-2-3), when a client and server share a PSK, TLS 1.3 allows clients to send data on the first flight ("early data") to speed up the request, effectively reducing the regular 1-RTT request to a 0-RTT request. -Make sure to enable fiber's `EnableTrustedProxyCheck` config option before using this middleware in order to not trust bogus HTTP request headers of the client. +Make sure to enable fiber's `TrustProxy` config option before using this middleware in order to not trust bogus HTTP request headers of the client. Also be aware that enabling support for early data in your reverse proxy (e.g. nginx, as done with a simple `ssl_early_data on;`) makes requests replayable. Refer to the following documents before continuing: diff --git a/docs/whats_new.md b/docs/whats_new.md index 6449d24292..e040f367d5 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -55,6 +55,8 @@ We have made several changes to the Fiber app, including: - EnablePrefork -> previously Prefork - EnablePrintRoutes - ListenerNetwork -> previously Network +- app.Config.EnabledTrustedProxyCheck -> has been moved to app.Config.TrustProxy + - TrustedProxies -> has been moved to TrustProxyConfig.Proxies ### new methods @@ -386,6 +388,35 @@ app.Get("*", static.New("./public/index.html")) You have to put `*` to the end of the route if you don't define static route with `app.Use`. ::: +#### Trusted Proxies + +We've renamed `EnableTrustedProxyCheck` to `TrustProxy` and moved `TrustedProxies` to `TrustProxyConfig`. + +```go +// Before +app := fiber.New(fiber.Config{ + // EnableTrustedProxyCheck enables the trusted proxy check. + EnableTrustedProxyCheck: true, + // TrustedProxies is a list of trusted proxy IP ranges/addresses. + TrustedProxies: []string{"0.8.0.0", "127.0.0.0/8", "::1/128"}, +}) +``` + +```go +// After +app := fiber.New(fiber.Config{ + // TrustProxy enables the trusted proxy check + TrustProxy: true, + // TrustProxyConfig allows for configuring trusted proxies. + TrustProxyConfig: fiber.TrustProxyConfig{ + // Proxies is a list of trusted proxy IP ranges/addresses. + Proxies: []string{"0.8.0.0"}, + // Trust all loop-back IP addresses (127.0.0.0/8, ::1/128) + Loopback: true, + } +}) +``` + ### ๐Ÿ—บ Router The signatures for [`Add`](#middleware-registration) and [`Route`](#route-chaining) have been changed. diff --git a/middleware/cors/config.go b/middleware/cors/config.go index 2613bab943..7432c4a9d5 100644 --- a/middleware/cors/config.go +++ b/middleware/cors/config.go @@ -41,7 +41,7 @@ type Config struct { // Optional. Default value []string{} AllowHeaders []string - // ExposeHeaders defines a whitelist headers that clients are allowed to + // ExposeHeaders defines an allowlist of headers that clients are allowed to // access. // // Optional. Default value []string{}. diff --git a/middleware/earlydata/earlydata_test.go b/middleware/earlydata/earlydata_test.go index 9a62bf2524..55e800ff2b 100644 --- a/middleware/earlydata/earlydata_test.go +++ b/middleware/earlydata/earlydata_test.go @@ -173,17 +173,19 @@ func Test_EarlyData(t *testing.T) { trustedRun(t, app) }) - t.Run("config with EnableTrustedProxyCheck", func(t *testing.T) { + t.Run("config with TrustProxy", func(t *testing.T) { app := appWithConfig(t, &fiber.Config{ - EnableTrustedProxyCheck: true, + TrustProxy: true, }) untrustedRun(t, app) }) - t.Run("config with EnableTrustedProxyCheck and trusted TrustedProxies", func(t *testing.T) { + t.Run("config with TrustProxy and trusted TrustProxyConfig.Proxies", func(t *testing.T) { app := appWithConfig(t, &fiber.Config{ - EnableTrustedProxyCheck: true, - TrustedProxies: []string{ - "0.0.0.0", + TrustProxy: true, + TrustProxyConfig: fiber.TrustProxyConfig{ + Proxies: []string{ + "0.0.0.0", + }, }, }) trustedRun(t, app)