Header limit of 32 KiB, upstream URL checks, build and run scripts
check / check (push) Successful in 1m46s
check / check (push) Successful in 1m46s
The request line and headers are now refused above 32 KiB exactly: Go's server reads 4 KiB past MaxHeaderBytes, so that is set to 28 KiB, and the tests try exactly 32 KiB and one byte more. SWWAF_UPSTREAM_URL now needs a host, and a given port must be from 1 to 65535. make build and make run call script/build and script/run. Model: opus-5-5
This commit is contained in:
@@ -71,7 +71,7 @@ var (
|
||||
errNotListenAddr = errors.New(
|
||||
"is not an address to listen on, such as :8080")
|
||||
errNotUpstreamURL = errors.New(
|
||||
"is not a URL with only a scheme, a host and a port, " +
|
||||
"is not a URL with only a scheme, a host and an optional port, " +
|
||||
"such as http://127.0.0.1:8081")
|
||||
)
|
||||
|
||||
@@ -325,8 +325,8 @@ func parseListenAddr(value string) (string, error) {
|
||||
}
|
||||
|
||||
// parseUpstreamURL reads the app's URL: http or https, a host and an
|
||||
// optional port, and nothing else, since the request's own path and
|
||||
// query go to the app unchanged.
|
||||
// optional port from 1 to 65535, and nothing else, since the request's
|
||||
// own path and query go to the app unchanged.
|
||||
func parseUpstreamURL(value string) (*url.URL, error) {
|
||||
upstream, err := url.Parse(value)
|
||||
if err != nil {
|
||||
@@ -334,12 +334,19 @@ func parseUpstreamURL(value string) (*url.URL, error) {
|
||||
}
|
||||
|
||||
onlySchemeAndHost := (upstream.Scheme == "http" || upstream.Scheme == "https") &&
|
||||
upstream.Host != "" && upstream.User == nil && upstream.Opaque == "" &&
|
||||
upstream.Hostname() != "" && upstream.User == nil && upstream.Opaque == "" &&
|
||||
(upstream.Path == "" || upstream.Path == "/") &&
|
||||
upstream.RawQuery == "" && upstream.Fragment == ""
|
||||
if !onlySchemeAndHost {
|
||||
return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL)
|
||||
}
|
||||
|
||||
if upstream.Port() != "" {
|
||||
port, err := strconv.ParseUint(upstream.Port(), 10, 16)
|
||||
if err != nil || port == 0 {
|
||||
return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL)
|
||||
}
|
||||
}
|
||||
|
||||
return upstream, nil
|
||||
}
|
||||
|
||||
@@ -142,6 +142,9 @@ func TestInvalidValueStopsTheStart(t *testing.T) {
|
||||
{upstreamURL, "127.0.0.1:8081"},
|
||||
{upstreamURL, "ftp://127.0.0.1:8081"},
|
||||
{upstreamURL, "http://"},
|
||||
{upstreamURL, "http://:8081"},
|
||||
{upstreamURL, "http://127.0.0.1:0"},
|
||||
{upstreamURL, "http://127.0.0.1:99999"},
|
||||
{upstreamURL, "http://127.0.0.1:8081/app"},
|
||||
{upstreamURL, "http://127.0.0.1:8081/?a=1"},
|
||||
{upstreamURL, "http://user:secret@127.0.0.1:8081"},
|
||||
|
||||
@@ -309,7 +309,7 @@ func TestServerHasTheFixedLimits(t *testing.T) {
|
||||
ProcessLog: requestlog.NewProcessLogger(io.Discard),
|
||||
})
|
||||
|
||||
if server.Addr != ":8080" || server.MaxHeaderBytes != 32<<10 ||
|
||||
if server.Addr != ":8080" || server.MaxHeaderBytes != 28<<10 ||
|
||||
server.IdleTimeout != 2*time.Minute || server.ReadHeaderTimeout != time.Minute {
|
||||
t.Errorf("server listens on %q with header limit %d, idle time %s and "+
|
||||
"header timeout %s", server.Addr, server.MaxHeaderBytes,
|
||||
@@ -327,16 +327,23 @@ func TestRefusesHeadersOver32KiB(t *testing.T) {
|
||||
})
|
||||
addr, _ := startProxy(t, app.URL, nil)
|
||||
|
||||
// size counts every byte of the request: the request line, the
|
||||
// headers and the blank line that ends them.
|
||||
const (
|
||||
start = "GET / HTTP/1.1\r\nHost: app\r\nX-Large: "
|
||||
end = "\r\n\r\n"
|
||||
)
|
||||
|
||||
for _, tc := range []struct {
|
||||
headerSize int
|
||||
want int
|
||||
size int
|
||||
want int
|
||||
}{
|
||||
{headerSize: 30 << 10, want: http.StatusOK},
|
||||
{headerSize: 40 << 10, want: http.StatusRequestHeaderFieldsTooLarge},
|
||||
{size: 32 << 10, want: http.StatusOK},
|
||||
{size: 32<<10 + 1, want: http.StatusRequestHeaderFieldsTooLarge},
|
||||
} {
|
||||
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
||||
req.Header.Set("X-Large", strings.Repeat("a", tc.headerSize))
|
||||
wantStatus(t, do(t, req), tc.want)
|
||||
conn := dial(t, addr)
|
||||
send(t, conn, start+strings.Repeat("a", tc.size-len(start)-len(end))+end)
|
||||
wantStatus(t, readResponse(t, conn), tc.want)
|
||||
}
|
||||
|
||||
if calls.Load() != 1 {
|
||||
|
||||
@@ -15,11 +15,14 @@ import (
|
||||
|
||||
// The request line and headers a client may send, and how long a
|
||||
// kept-open client connection may wait for its next request, are fixed
|
||||
// rather than settings. The idle time is longer than the 90 seconds after
|
||||
// which traefik closes a connection it is not using, so traefik never
|
||||
// sends a request on a connection smallwebwaf is closing.
|
||||
// rather than settings. The limit on the request line and headers is
|
||||
// 32 KiB, but Go's server reads 4 KiB past its MaxHeaderBytes before it
|
||||
// refuses, so MaxHeaderBytes is set 4 KiB lower. The idle time is longer
|
||||
// than the 90 seconds after which traefik closes a connection it is not
|
||||
// using, so traefik never sends a request on a connection smallwebwaf is
|
||||
// closing.
|
||||
const (
|
||||
requestHeaderMaxBytes = 32 << 10
|
||||
requestHeaderMaxBytes = 32<<10 - 4<<10
|
||||
clientIdleTimeout = 120 * time.Second
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user