snapshot of https://github.com/go-chi/chi.git (master, 2026-09-15; blobs > 131072B omitted)
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
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
This commit is contained in:
108
middleware/client_ip_example_test.go
Normal file
108
middleware/client_ip_example_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user