middleware: take the client address from the right of X-Forwarded-For (closes #181)
check / check (push) Successful in 1m9s
check / check (push) Successful in 1m9s
realIP took the first X-Forwarded-For entry, which the client itself can write, so behind a proxy that appends to the header a client chose the address dnswatcher logs and the /metrics rate limit counts. It now walks the entries from the right past trusted proxies, using the existing trusted-proxy check, and takes the first that is not one; the leftmost when all are. All X-Forwarded-For header lines are read as one list, since a proxy may add its own line instead of appending to the client's. An empty entry where the client address belongs falls back to the peer address, as an empty first entry did before. X-Real-IP is unchanged. Model: opus-5-5
This commit is contained in:
@@ -209,6 +209,12 @@ func isTrustedProxy(ip net.IP) bool {
|
||||
|
||||
// realIP extracts the client's real IP address from the request.
|
||||
// Proxy headers are only trusted from RFC1918/loopback addresses.
|
||||
//
|
||||
// Each proxy adds to the end of X-Forwarded-For the address it got the
|
||||
// request from, so the client can write every entry before the one the
|
||||
// first trusted proxy added. The client address is therefore the
|
||||
// rightmost entry that is not a trusted proxy, or the leftmost entry
|
||||
// when they all are.
|
||||
func realIP(r *http.Request) string {
|
||||
addr := ipFromHostPort(r.RemoteAddr)
|
||||
remoteIP := net.ParseIP(addr)
|
||||
@@ -223,16 +229,26 @@ func realIP(r *http.Request) string {
|
||||
return ip
|
||||
}
|
||||
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
if parts := strings.SplitN(
|
||||
xff, ",", 2, //nolint:mnd
|
||||
); len(parts) > 0 {
|
||||
if ip := strings.TrimSpace(parts[0]); ip != "" {
|
||||
return ip
|
||||
}
|
||||
// A proxy may add its entry as a header line of its own instead of
|
||||
// appending to the line the client sent, so all lines form one list.
|
||||
entries := strings.Split(
|
||||
strings.Join(r.Header.Values("X-Forwarded-For"), ","), ",",
|
||||
)
|
||||
client := strings.TrimSpace(entries[0])
|
||||
|
||||
for i := len(entries) - 1; i > 0; i-- {
|
||||
entry := strings.TrimSpace(entries[i])
|
||||
if !isTrustedProxy(net.ParseIP(entry)) {
|
||||
client = entry
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if client != "" {
|
||||
return client
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user