mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
fix: correctly parse and validate forwarded client IPs and remove unmaintained chi-middleware/proxy (#39190)
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>
This commit is contained in:
1 parent
5129ad5439
commit
5e4ed4d508
7 files changed
+215
-24
No files matched your search
@@ -0,0 +1,57 @@
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user