Files
upstream-chi/middleware/client_ip_example_test.go
upstream-mirror 72c9f2ee75
Some checks failed
Test / test (1.24.x, ubuntu-latest) (push) Successful in 2m56s
Test / test (1.25.x, ubuntu-latest) (push) Successful in 2m15s
Test / test (1.26.x, ubuntu-latest) (push) Successful in 2m23s
Test / test (1.27.x, ubuntu-latest) (push) Failing after 3m56s
Test / test (1.24.x, windows-latest) (push) Has been cancelled
Test / test (1.25.x, windows-latest) (push) Has been cancelled
Test / test (1.26.x, windows-latest) (push) Has been cancelled
Test / test (1.27.x, windows-latest) (push) Has been cancelled
snapshot of https://github.com/go-chi/chi.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:48:17 +05:00

109 lines
3.8 KiB
Go

package middleware_test
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
// Example_clientIP shows how to choose a ClientIPFrom* middleware. There is
// no safe default — pick exactly ONE based on your network setup:
//
// - [middleware.ClientIPFromRemoteAddr] — this server is directly on the
// public internet, with no reverse proxy in front.
//
// - [middleware.ClientIPFromHeader] — your reverse proxy sets a dedicated
// single-IP header (X-Real-IP, CF-Connecting-IP, X-Client-IP) and
// overwrites any client-supplied value for every request.
//
// - [middleware.ClientIPFromXFF] — you sit behind one or more reverse
// proxies whose IP ranges you can enumerate (your VPC CIDR, Cloudflare's
// published IP list, etc.).
//
// - [middleware.ClientIPFromXFFTrustedProxies] — you sit behind a known,
// fixed number of reverse proxies whose IPs are dynamic (autoscaling
// pools, ephemeral containers).
//
// Read the resulting IP with [middleware.GetClientIP] (string) or
// [middleware.GetClientIPAddr] ([net/netip.Addr]). These middlewares never
// mutate [net/http.Request.RemoteAddr].
//
// The legacy [middleware.RealIP] middleware is deprecated; it is vulnerable
// to IP spoofing (GHSA-3fxj-6jh8-hvhx, GHSA-rjr7-jggh-pgcp, GHSA-9g5q-2w5x-hmxf).
//
// Background:
// https://github.com/go-chi/chi/pull/967
// https://adam-p.ca/blog/2022/03/x-forwarded-for/
func Example_clientIP() {
r := chi.NewRouter()
// Pick ONE; the others are shown commented for reference.
// (1) Behind a reverse proxy that sets a single-IP header for every
// request and overwrites any client-supplied value (Cloudflare, Nginx
// with ngx_http_realip_module, Apache with mod_remoteip, ...).
r.Use(middleware.ClientIPFromHeader("CF-Connecting-IP"))
// (2) Behind reverse proxies whose IP ranges you can enumerate as CIDRs.
// The middleware walks X-Forwarded-For right-to-left, skipping trusted
// entries; fail-closed on garbage.
//
// r.Use(middleware.ClientIPFromXFF(
// "13.32.0.0/15", // CloudFront IPv4.
// "2600:9000::/28", // CloudFront IPv6.
// ))
// (3) Behind a known number of trusted reverse proxies whose IPs are
// dynamic.
//
// r.Use(middleware.ClientIPFromXFFTrustedProxies(2))
// (4) Directly on the public internet, no reverse proxy in front.
//
// r.Use(middleware.ClientIPFromRemoteAddr)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, middleware.GetClientIP(r.Context()))
})
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("CF-Connecting-IP", "198.51.100.42")
req.Header.Set("X-Forwarded-For", "1.2.3.4") // attacker-supplied; ignored.
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
fmt.Print(w.Body.String())
// Output: 198.51.100.42
}
// Example_clientIPFromXFFTrustedProxies shows that with two proxies the
// client sits at XFF[len-2] and any client-prepended entries to the left of
// that position are ignored.
func Example_clientIPFromXFFTrustedProxies() {
r := chi.NewRouter()
r.Use(middleware.ClientIPFromXFFTrustedProxies(2)) // P1 (inner) + P2 (outer).
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, middleware.GetClientIP(r.Context()))
})
// Each chain ends in "<client>, <P1>"; P2's address is in RemoteAddr,
// not in X-Forwarded-For. Leading entries are client-controlled.
for _, xff := range []string{
"109.81.118.93, 3.172.119.73",
"8.8.8.8, 109.81.118.93, 3.172.119.73",
"8.8.8.8, 8.8.8.8, 8.8.8.8, 109.81.118.93, 3.172.119.73",
} {
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("X-Forwarded-For", xff)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
fmt.Print(w.Body.String())
}
// Output:
// 109.81.118.93
// 109.81.118.93
// 109.81.118.93
}