Merge 7c53bb23c55bf273894273add863eb1085638aa7 into b3455fa99a5e8d07133d5140017ec7c49f032a07

This commit is contained in:
Victor Song 2025-03-24 22:12:46 +00:00 committed by GitHub
commit cffd7e7229
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 88 additions and 0 deletions

View File

@ -746,6 +746,14 @@ func (rp *reverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Out.URL.RawPath = rp.url.RawPath
}
// ReverseProxy.ServeHTTP strips out unparseable query parameters
// as it could be a security concern. To preserve downstream use
// of query parameters, we copy them back if the input and output
// do not match
if r.In.URL.RawQuery != r.Out.URL.RawQuery {
r.Out.URL.RawQuery = r.In.URL.RawQuery
}
r.Out.Host = r.In.Host
addProxyForwardedHeaders(r)
rp.lb.addTailscaleIdentityHeaders(r)

View File

@ -757,6 +757,86 @@ func TestServeHTTPProxyHeaders(t *testing.T) {
}
}
func TestServeHTTPProxyQuery(t *testing.T) {
b := newTestBackend(t)
// Start test serve endpoint.
testServ := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// Set the raw request query parameter to a response header, so
// the final output can can be checked in tests.
t.Logf("adding query parameters %s", r.URL.RawQuery)
w.Header().Add("QueryParam", r.URL.RawQuery)
},
))
defer testServ.Close()
// The input query parameters should always be the same as the output query parameters.
// For this reason, the tests only include the input query parameters
// (rather than both the input and expected query parameters).
tests := []struct {
name string
in string
}{
{
name: "single well-formed query parameter",
in: "key=value",
},
{
name: "single malformed query parameter",
in: "key=value;nonsense",
},
{
name: "one malformed and one well-formed query parameter",
in: "key1=value1;nonsense&key2=value2",
},
{
name: "one well-formed, one malformed, and another well-formed query parameter",
in: "key1=value1&key2=value2;nonsense&key3=value3",
},
{
name: "one malformed and one well-formed query parameter (this time with percent sign)",
in: "key1=value1%yes%%&key2=value2",
},
}
const testPath = "/foo"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conf := &ipn.ServeConfig{
Web: map[ipn.HostPort]*ipn.WebServerConfig{
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
testPath: {Proxy: testServ.URL + testPath},
}},
},
}
if err := b.SetServeConfig(conf, ""); err != nil {
t.Fatal(err)
}
req := &http.Request{
URL: &url.URL{Path: testPath, RawQuery: tt.in},
TLS: &tls.ConnectionState{ServerName: "example.ts.net"},
}
req = req.WithContext(serveHTTPContextKey.WithValue(req.Context(),
&serveHTTPContext{
DestPort: 443,
SrcAddr: netip.MustParseAddrPort("1.2.3.4:1234"),
}))
w := httptest.NewRecorder()
b.serveWebHandler(w, req)
q := w.Result().Header.Get("QueryParam")
if q != tt.in {
t.Errorf("wanted query params %q got %q", tt.in, q)
}
})
}
}
func Test_reverseProxyConfiguration(t *testing.T) {
b := newTestBackend(t)
type test struct {