httpservice: improve validation of proxied URLs (#29600)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
979522d309
Коммит
8c41ec75db
@@ -7,9 +7,14 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpproxy"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -155,6 +160,20 @@ func dialContextFilter(dial DialContextFunction, allowHost func(host string) boo
|
||||
}
|
||||
}
|
||||
|
||||
func getProxyFn() func(r *http.Request) (*url.URL, error) {
|
||||
proxyFromEnvFn := httpproxy.FromEnvironment().ProxyFunc()
|
||||
return func(r *http.Request) (*url.URL, error) {
|
||||
// TODO: Consider removing this code once MM-61938 is fixed upstream.
|
||||
if r.URL != nil {
|
||||
if addr, err := netip.ParseAddr(r.URL.Hostname()); err == nil && addr.Is6() && addr.Zone() != "" {
|
||||
return nil, fmt.Errorf("invalid IPv6 address in URL: %q", addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
return proxyFromEnvFn(r.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func NewTransport(enableInsecureConnections bool, allowHost func(host string) bool, allowIP func(ip net.IP) bool) *MattermostTransport {
|
||||
dialContext := (&net.Dialer{
|
||||
Timeout: ConnectTimeout,
|
||||
@@ -167,7 +186,7 @@ func NewTransport(enableInsecureConnections bool, allowHost func(host string) bo
|
||||
|
||||
return &MattermostTransport{
|
||||
&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Proxy: getProxyFn(),
|
||||
DialContext: dialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
|
||||
@@ -110,6 +110,17 @@ func TestHTTPClientWithProxy(t *testing.T) {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "proxy", string(body))
|
||||
|
||||
t.Run("invalid IPv6 address in proxied URL", func(t *testing.T) {
|
||||
c := NewHTTPClient(NewTransport(true, nil, nil))
|
||||
|
||||
t.Setenv("HTTP_PROXY", "http://proxy.example.org")
|
||||
t.Setenv("HTTPS_PROXY", "https://proxy.example.org")
|
||||
t.Setenv("NO_PROXY", ".example.com")
|
||||
|
||||
_, err := c.Get("http://[fe80::8e87:5021:6f6c:605e%25eth0]")
|
||||
require.EqualError(t, err, `Get "http://[fe80::8e87:5021:6f6c:605e%25eth0]": invalid IPv6 address in URL: "fe80::8e87:5021:6f6c:605e%eth0"`)
|
||||
})
|
||||
}
|
||||
|
||||
func createProxyServer() *httptest.Server {
|
||||
@@ -261,3 +272,95 @@ func TestSplitHostnames(t *testing.T) {
|
||||
hostnames = strings.FieldsFunc(config, splitFields)
|
||||
require.Equal(t, []string{"127.0.0.1", "localhost", "192.168.1.0"}, hostnames)
|
||||
}
|
||||
|
||||
func TestGetProxyFn(t *testing.T) {
|
||||
t.Setenv("HTTP_PROXY", "http://proxy.example.org")
|
||||
t.Setenv("HTTPS_PROXY", "https://proxy.example.org")
|
||||
t.Setenv("NO_PROXY", ".example.com")
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
input string
|
||||
output string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
},
|
||||
{
|
||||
name: "no proxy",
|
||||
input: "http://test.example.com",
|
||||
},
|
||||
{
|
||||
name: "localhost",
|
||||
input: "http://localhost",
|
||||
},
|
||||
{
|
||||
name: "hostname",
|
||||
input: "http://example.org",
|
||||
output: "http://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "hostname with port",
|
||||
input: "http://example.org:4545",
|
||||
output: "http://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "https",
|
||||
input: "https://example.org",
|
||||
output: "https://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "ipv4",
|
||||
input: "http://10.0.0.45",
|
||||
output: "http://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "ipv4 with port",
|
||||
input: "http://10.0.0.45:4545",
|
||||
output: "http://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "ipv6",
|
||||
input: "http://[fe80::8e87:5021:6f6c:605e]",
|
||||
output: "http://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "ipv6 with port",
|
||||
input: "http://[fe80::8e87:5021:6f6c:605e]:4545",
|
||||
output: "http://proxy.example.org",
|
||||
},
|
||||
{
|
||||
name: "ipv6 with zone",
|
||||
input: "http://[fe80::8e87:5021:6f6c:605e%25eth0]",
|
||||
output: "http://proxy.example.org",
|
||||
err: `invalid IPv6 address in URL: "fe80::8e87:5021:6f6c:605e%eth0"`,
|
||||
},
|
||||
{
|
||||
name: "ipv6 with zone and port",
|
||||
input: "http://[fe80::8e87:5021:6f6c:605e%25eth0]:4545",
|
||||
output: "http://proxy.example.org",
|
||||
err: `invalid IPv6 address in URL: "fe80::8e87:5021:6f6c:605e%eth0"`,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
inURL, err := url.Parse(tc.input)
|
||||
require.NoError(t, err)
|
||||
outURL, err := getProxyFn()(&http.Request{
|
||||
URL: inURL,
|
||||
})
|
||||
if tc.err != "" { // error case
|
||||
require.EqualError(t, err, tc.err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if tc.output == "" { // not proxied case
|
||||
require.Nil(t, outURL)
|
||||
} else { // proxied case
|
||||
require.NotNil(t, outURL)
|
||||
require.Equal(t, tc.output, outURL.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user