Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥Feature: Add support for TrustProxy #3170

Merged
merged 11 commits into from
Oct 17, 2024
6 changes: 4 additions & 2 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})

Expand Down
71 changes: 54 additions & 17 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
gaby marked this conversation as resolved.
Show resolved Hide resolved
// 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,
// 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,
xEricL marked this conversation as resolved.
Show resolved Hide resolved
// 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.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
Expand All @@ -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
Expand All @@ -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"`
}
gaby marked this conversation as resolved.
Show resolved Hide resolved

// RouteMessage is some message need to be print when server starts
type RouteMessage struct {
name string
Expand Down Expand Up @@ -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 {
gaby marked this conversation as resolved.
Show resolved Hide resolved
app.handleTrustedProxy(ipAddress)
}

Expand All @@ -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)
gaby marked this conversation as resolved.
Show resolved Hide resolved
}
} 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{}{}
}
gaby marked this conversation as resolved.
Show resolved Hide resolved
}
gaby marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down
22 changes: 14 additions & 8 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
gaby marked this conversation as resolved.
Show resolved Hide resolved
func (c *DefaultCtx) Host() string {
if c.IsProxyTrusted() {
if host := c.Get(HeaderXForwardedHost); len(host) > 0 {
Expand All @@ -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())

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
gaby marked this conversation as resolved.
Show resolved Hide resolved
}

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
}
Expand Down
10 changes: 5 additions & 5 deletions ctx_interface_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading