Client address is the right-most untrusted X-Forwarded-For entry (closes #144)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
From a peer in TRUSTED_PROXIES, clientIP took the left-most X-Forwarded-For entry, which the client writes itself behind a proxy that appends to the header, so a client could choose the address the rate limit counts and the request log records. It now walks the entries from the right past trusted proxies and takes the first that is not one. An entry written as an address with a port is read as that address; with only trusted entries, or one that is not an address at all, it uses the peer. Every X-Forwarded-For header line is read. X-Real-IP is read only when there is no X-Forwarded-For entry. README.md Settings says which entry is taken. Model: opus-5-5
This commit is contained in:
@@ -25,6 +25,9 @@ import (
|
||||
const (
|
||||
// loopbackPeer is a remote address inside the trusted-proxy allowlist.
|
||||
loopbackPeer = "127.0.0.1:5000"
|
||||
// loopbackIP is the address of loopbackPeer, which ClientIP returns
|
||||
// when it uses the peer.
|
||||
loopbackIP = "127.0.0.1"
|
||||
// forwardedIP is the client address presented via X-Forwarded-For.
|
||||
forwardedIP = "203.0.113.7"
|
||||
// realIP is the client address presented via X-Real-IP.
|
||||
@@ -85,10 +88,53 @@ func clientIPCases() []clientIPCase {
|
||||
want: forwardedIP,
|
||||
},
|
||||
{
|
||||
name: "trusted proxy uses left-most of chain",
|
||||
remoteAddr: "10.1.2.3:5000",
|
||||
xff: forwardedIP + ", 10.1.2.3",
|
||||
want: forwardedIP,
|
||||
name: "trusted proxy uses right-most of chain",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "203.0.113.9, 198.51.100.7",
|
||||
want: "198.51.100.7",
|
||||
},
|
||||
{
|
||||
name: "trusted proxy entries are skipped from the right",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "203.0.113.9, 10.0.0.5",
|
||||
want: "203.0.113.9",
|
||||
},
|
||||
{
|
||||
name: "chain of only trusted proxies uses peer",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "10.0.0.5, 10.1.2.3",
|
||||
want: loopbackIP,
|
||||
},
|
||||
{
|
||||
name: "entry with a port is read as its address",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "203.0.113.9, 198.51.100.7:5000",
|
||||
want: "198.51.100.7",
|
||||
},
|
||||
{
|
||||
name: "ipv6 entry with a port is read as its address",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "203.0.113.9, [2001:db8::1]:443",
|
||||
want: "2001:db8::1",
|
||||
},
|
||||
{
|
||||
name: "trusted proxy entry with a port is skipped",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "203.0.113.9, 10.0.0.5:5000",
|
||||
want: "203.0.113.9",
|
||||
},
|
||||
{
|
||||
name: "entry that is not an address uses peer",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "203.0.113.9, unknown",
|
||||
want: loopbackIP,
|
||||
},
|
||||
{
|
||||
name: "x-real-ip ignored when forwarded-for has entries",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "10.0.0.5",
|
||||
xRealIP: realIP,
|
||||
want: loopbackIP,
|
||||
},
|
||||
{
|
||||
name: "trusted proxy falls back to x-real-ip",
|
||||
@@ -117,7 +163,7 @@ func clientIPCases() []clientIPCase {
|
||||
name: "trusted proxy with garbage header uses peer",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "not-an-ip",
|
||||
want: "127.0.0.1",
|
||||
want: loopbackIP,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -148,6 +194,23 @@ func TestClientIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientIPReadsEveryForwardedForLine checks that an entry a proxy
|
||||
// adds as an X-Forwarded-For line of its own is read, and the line the
|
||||
// client sent before it is not taken.
|
||||
func TestClientIPReadsEveryForwardedForLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "203.0.113.9")
|
||||
header.Add("X-Forwarded-For", "198.51.100.7")
|
||||
|
||||
got := middleware.ClientIP(loopbackPeer, header,
|
||||
mustPrefixes(t, "127.0.0.1/32"))
|
||||
if got != "198.51.100.7" {
|
||||
t.Errorf("ClientIP() = %q, want %q", got, "198.51.100.7")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityHeaders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user