Skip to content

Commit 74a1d30

Browse files
Add docs from gofiber/fiber@4ff4494
1 parent 836b76d commit 74a1d30

1 file changed

Lines changed: 76 additions & 5 deletions

File tree

docs/core/middleware/proxy.md

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,56 @@ func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handle
2929

3030
## Security
3131

32-
The `Forward`, `DomainForward`, and `BalancerForward` functions automatically set the `X-Real-IP` header to the actual client IP address obtained from `c.IP()` before forwarding the request upstream. This protects against IP spoofing attacks where a malicious client attempts to forge their IP address by sending a fake `X-Real-IP` header. Any existing `X-Real-IP` header on the incoming request is overwritten with the real client IP. Note that `DomainForward` only applies this overwrite when the request host matches the configured hostname; non-matching requests are not forwarded and are passed through unchanged.
32+
The proxy middleware applies several defenses by default. They can be relaxed via `Config.SecurityPolicy` (for `Balancer`) or `proxy.WithSecurityPolicy` (for the runtime helpers `Do`, `Forward`, `DoRedirects`, `DoTimeout`, `DoDeadline`).
3333

34-
If you're using the `Balancer` function with the `Config` struct, you can achieve the same protection by using the `ModifyRequest` callback as shown in the examples below.
34+
### SSRF protection
3535

36-
When using `Do`, `DoRedirects`, `DoDeadline`, or `DoTimeout` directly, the `X-Real-IP` header is not automatically set. You should set it manually if your upstream server requires it:
36+
Upstream addresses that resolve to loopback, RFC 1918 private, link-local (including the `169.254.169.254` cloud-metadata address), multicast, unspecified, or RFC 6598 CGNAT ranges are rejected with `ErrUpstreamHostBlocked`. If any resolved IP falls in a blocked range the upstream is rejected, mitigating DNS-rebinding attempts that return a mix of public and private answers.
37+
38+
For `Balancer`, the resolved IP is re-validated at **dial time** (via a guarded `Dial` on each upstream `fasthttp.HostClient`), which both defeats DNS-rebinding and avoids resolving hostnames at startup — a transient DNS failure won't panic your application. DNS lookups are bounded by a 5-second timeout.
39+
40+
:::caution DNS-rebinding scope
41+
The dial-time re-validation only applies to `Balancer`, because those `HostClient`s are constructed by the middleware. The runtime helpers — `Do`, `DoRedirects`, `DoTimeout`, `DoDeadline`, `Forward`, `DomainForward`, and `BalancerForward` — validate the upstream host up front, then dispatch through the shared or user-supplied `*fasthttp.Client`, which re-resolves the name without the guard. Against a **rebinding-capable resolver** these paths have a check/use window and are not fully mitigated. If that is part of your threat model, use `Balancer` (with `AllowPrivateIPs = false`), or supply a client whose `Dial` performs its own resolved-IP validation.
42+
:::
43+
44+
Set `SecurityPolicy.AllowPrivateIPs = true` to opt out — required when proxying to internal services on the same network.
45+
46+
### Scheme allowlist
47+
48+
Only `http` and `https` upstream schemes are accepted by default; `file://`, `gopher://`, `ftp://`, and other schemes are rejected. Override via `SecurityPolicy.AllowedSchemes`.
49+
50+
### HTTPS-to-HTTP redirect downgrades
51+
52+
`DoRedirects` rejects redirects from HTTPS origins to plaintext HTTP targets with `ErrRedirectDowngrade`. Following such a redirect would leak any cookies or `Authorization` headers established under TLS. Set `SecurityPolicy.AllowHTTPSDowngrade = true` to override.
53+
54+
When a redirect crosses to a **different host**, `DoRedirects` strips the `Authorization`, `Proxy-Authorization`, and `Cookie` headers so credentials bound to the original origin are not forwarded to a third-party upstream. Same-host redirects retain these headers.
55+
56+
### RFC 7230 hop-by-hop header stripping
57+
58+
`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailer`, `Transfer-Encoding`, and `Upgrade` are stripped from both the outbound request and the inbound response, along with every header listed in the `Connection` field per RFC 7230 §6.1. This prevents request smuggling (`TE`/`Transfer-Encoding`), proxy-credential forwarding, and protocol-upgrade leaks. The legacy `KeepConnectionHeader` option preserves only the literal `Connection` header for backwards compatibility; the other hop-by-hop headers are still stripped. To preserve every hop-by-hop header (not recommended), set `SecurityPolicy.KeepHopByHopHeaders = true`.
59+
60+
### TLS minimum version
61+
62+
`Config.TLSConfig` is cloned with `MinVersion: tls.VersionTLS12` if no minimum is configured, so deprecated TLS versions cannot be negotiated by accident.
63+
64+
### Response body size and connection caps
65+
66+
`Config.MaxResponseBodySize` bounds upstream response bodies to protect against memory exhaustion. `Config.MaxConnsPerHost` (default `1024`) caps concurrent connections per upstream to limit fan-out from a single hot host.
67+
68+
### X-Real-IP spoof prevention
69+
70+
`Forward`, `DomainForward`, and `BalancerForward` automatically overwrite the `X-Real-IP` header with `c.IP()` before forwarding, so clients cannot spoof their address. `DomainForward` only applies the overwrite when the request host matches the configured hostname (matched case-insensitively per RFC 9110 §4.2.3); non-matching requests are passed through unchanged.
71+
72+
If you're using `Balancer` with the `Config` struct, you can replicate the protection in `ModifyRequest`. When using `Do`, `DoRedirects`, `DoDeadline`, or `DoTimeout` directly, the `X-Real-IP` header is not set automatically — set it manually if needed:
3773

3874
```go
3975
c.Request().Header.Set("X-Real-IP", c.IP())
4076
```
4177

78+
### Path concatenation safety
79+
80+
`DomainForward` and `BalancerForward` previously concatenated the configured upstream with `c.OriginalURL()`. Crafted request paths beginning with `//` could exploit URL parsing to redirect the proxy at a different host (network-path reference injection). The proxy now sanitises the joined path so the upstream host pinned in configuration is preserved regardless of the inbound request.
81+
4282
## Examples
4383

4484
Import the middleware package:
@@ -59,11 +99,24 @@ proxy.WithClient(&fasthttp.Client{
5999
DisablePathNormalizing: true,
60100
MaxConnsPerHost: 2048,
61101
// Allow self-signed certificates when proxying to HTTPS targets.
102+
// SECURITY: disables certificate verification — use only when the
103+
// upstream is on a trusted network.
62104
TLSConfig: &tls.Config{
63105
InsecureSkipVerify: true,
106+
MinVersion: tls.VersionTLS12,
64107
},
65108
})
66109

110+
// Relax SSRF protection for local development against loopback servers.
111+
// SECURITY: in production, leave AllowPrivateIPs false (the default) and
112+
// list explicit upstream hosts so the proxy cannot be coerced into
113+
// reaching internal services or cloud-metadata endpoints.
114+
prev := proxy.WithSecurityPolicy(proxy.SecurityPolicy{
115+
AllowedSchemes: []string{"http", "https"},
116+
AllowPrivateIPs: true,
117+
})
118+
defer proxy.WithSecurityPolicy(prev)
119+
67120
// Forward requests for a specific domain with proxy.DomainForward.
68121
app.Get("/payments", proxy.DomainForward("docs.gofiber.io", "http://localhost:8000"))
69122

@@ -181,10 +234,12 @@ app.Use(proxy.Balancer(proxy.Config{
181234
| MaxConnsPerHost | `int` | Maximum number of connections per upstream host. The default proxy client and balancer host clients use this limit unless you override it with `WithClient`, a per-handler client, or `proxy.Config`. | `1024` |
182235
| 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). | (Not specified) |
183236
| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | (Not specified) |
184-
| KeepConnectionHeader | `bool` | Keeps the `Connection` header when set to `true`. By default the header is removed to comply with RFC 7230 §6.1 and avoid proxy loops. | `false` |
185-
| TLSConfig | `*tls.Config` | TLS config for the HTTP client. | `nil` |
237+
| KeepConnectionHeader | `bool` | Keeps the `Connection` header when set to `true`. By default the header is removed to comply with RFC 7230 §6.1 and avoid proxy loops. Other hop-by-hop headers are still stripped regardless of this setting. | `false` |
238+
| TLSConfig | `*tls.Config` | TLS config for the HTTP client. Cloned with `MinVersion: tls.VersionTLS12` when no minimum is set. | `nil` |
186239
| DialDualStack | `bool` | Client will attempt to connect to both IPv4 and IPv6 host addresses if set to true. | `false` |
187240
| Client | `*fasthttp.LBClient` | Client is a custom client when client config is complex. | `nil` |
241+
| SecurityPolicy | `*SecurityPolicy` | Overrides the default SSRF, redirect, and hop-by-hop header rules for this balancer. When `nil`, the package-level policy set via `WithSecurityPolicy` is used. See [Security](#security). | `nil` |
242+
| MaxResponseBodySize | `int` | Maximum upstream response body size in bytes. `0` keeps fasthttp's unlimited default. | `0` |
188243

189244
## Default Config
190245

@@ -198,3 +253,19 @@ var ConfigDefault = Config{
198253
KeepConnectionHeader: false,
199254
}
200255
```
256+
257+
## Default SecurityPolicy
258+
259+
When `Config.SecurityPolicy` is `nil` (and `proxy.WithSecurityPolicy` has not been called), the package falls back to the value returned by `proxy.DefaultSecurityPolicy()`:
260+
261+
```go
262+
// DefaultSecurityPolicy returns the secure-by-default policy.
263+
func DefaultSecurityPolicy() proxy.SecurityPolicy {
264+
return proxy.SecurityPolicy{
265+
AllowedSchemes: []string{"http", "https"},
266+
AllowPrivateIPs: false,
267+
AllowHTTPSDowngrade: false,
268+
KeepHopByHopHeaders: false,
269+
}
270+
}
271+
```

0 commit comments

Comments
 (0)