mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
Replace the unmaintained `chi-middleware/proxy` with an in-tree handler that detects client IPs correctly behind common reverse proxies. 1. Handle repeated header lines and comma-separated proxy hops. 2. Accept and normalize IPs, including `ip:port`. 3. Keep the client port from the header value or `X-Real-Port` instead of logging port 0. 4. Prefer the last `X-Real-IP` line. 5. Keep the proxy's address when the selected value is invalid. 6. Read `Forwarded` only when `X-Forwarded-For` is absent. Docs PR: https://gitea.com/gitea/docs/pulls/560 Fixes: https://github.com/go-gitea/gitea/issues/35682 Fixes: https://github.com/go-gitea/gitea/issues/36814 Co-authored-by: bircni <bircni@icloud.com>
58 lines
3.6 KiB
Go
58 lines
3.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package common
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestForwardedHeadersHandler(t *testing.T) {
|
|
defaultProxies := []string{"127.0.0.0/8", "::1/128"}
|
|
cases := []struct {
|
|
name string
|
|
remoteAddr string
|
|
limit int
|
|
proxies []string
|
|
header http.Header
|
|
expected string
|
|
}{
|
|
{"chain shorter than limit", "127.0.0.1:1234", 3, defaultProxies, http.Header{"X-Forwarded-For": {"1.1.1.1, 2.2.2.2"}}, "1.1.1.1:0"},
|
|
{"repeated header without spaces", "127.0.0.1:1234", 2, defaultProxies, http.Header{"X-Forwarded-For": {"1.1.1.1,2.2.2.2", "3.3.3.3"}}, "2.2.2.2:0"},
|
|
{"non-ip entry", "127.0.0.1:1234", 1, defaultProxies, http.Header{"X-Forwarded-For": {"evil, <script>"}}, "127.0.0.1:1234"},
|
|
{"mapped ipv4", "[::ffff:127.0.0.1]:1234", 1, defaultProxies, http.Header{"X-Forwarded-For": {"::ffff:1.2.3.4"}}, "1.2.3.4:0"},
|
|
{"entry with port", "127.0.0.1:1234", 1, defaultProxies, http.Header{"X-Forwarded-For": {"[2001:db8::1]:443"}}, "[2001:db8::1]:443"},
|
|
{"zoned ipv6 entry", "[::1]:1234", 1, defaultProxies, http.Header{"X-Forwarded-For": {"2001:db8::1%eth0"}}, "[2001:db8::1]:0"},
|
|
{"last real ip and port win", "127.0.0.1:1234", 1, defaultProxies, http.Header{"X-Real-Ip": {"8.8.8.8", "9.9.9.9"}, "X-Real-Port": {"1", "4321"}, "X-Forwarded-For": {"1.1.1.1"}}, "9.9.9.9:4321"},
|
|
{"empty real ip falls through", "127.0.0.1:1234", 1, defaultProxies, http.Header{"X-Real-Ip": {""}, "X-Forwarded-For": {"1.1.1.1"}}, "1.1.1.1:0"},
|
|
{"forwarded ignored with x-forwarded-for", "127.0.0.1:1234", 1, defaultProxies, http.Header{"X-Forwarded-For": {"1.1.1.1"}, "Forwarded": {"for=6.6.6.6"}}, "1.1.1.1:0"},
|
|
{"forwarded hops skip quoted separators, empty elements and malformed prefix", "127.0.0.1:1234", 2, defaultProxies, http.Header{"Forwarded": {`for="x, For="[2001:db8::1]:4711";host="a,b;for=6.6.6.6"`, `, for=_hidden`}}, "[2001:db8::1]:4711"},
|
|
{"forwarded quoted pair and obfuscated port", "127.0.0.1:1234", 1, defaultProxies, http.Header{"Forwarded": {`for="192.0.2\.43:_port"`}}, "192.0.2.43:0"},
|
|
{"forwarded repeated for", "127.0.0.1:1234", 1, defaultProxies, http.Header{"Forwarded": {"for=1.1.1.1;for=2.2.2.2"}}, "127.0.0.1:1234"},
|
|
{"untrusted peer", "8.8.8.8:1234", 1, defaultProxies, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "8.8.8.8:1234"},
|
|
{"zoned peer", "[fe80::1%en1]:1234", 1, []string{"fe80::1%en0"}, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "[fe80::1%en1]:1234"},
|
|
{"bare ip proxy after invalid one", "10.0.0.1:1234", 1, []string{"invalid", "10.0.0.1"}, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "1.1.1.1:0"},
|
|
{"mapped cidr proxy", "10.0.0.1:1234", 1, []string{"::ffff:10.0.0.0/104"}, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "1.1.1.1:0"},
|
|
{"mapped cidr spanning native ipv6", "[::1]:1234", 1, []string{"::ffff:10.0.0.0/80"}, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "1.1.1.1:0"},
|
|
{"wildcard proxy with zoned peer", "[fe80::1%en0]:1234", 1, []string{"*"}, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "1.1.1.1:0"},
|
|
{"unix socket", "@", 1, defaultProxies, http.Header{"X-Forwarded-For": {"1.1.1.1"}}, "1.1.1.1:0"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
var got string
|
|
handler := ForwardedHeadersHandler(c.limit, c.proxies)(http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
|
|
got = req.RemoteAddr
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.RemoteAddr = c.remoteAddr
|
|
req.Header = c.header
|
|
handler.ServeHTTP(httptest.NewRecorder(), req)
|
|
assert.Equal(t, c.expected, got)
|
|
})
|
|
}
|
|
}
|