Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d8d570d5c |
@@ -1,11 +1,14 @@
|
|||||||
package proxy_test
|
package proxy_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/synctest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||||
@@ -237,6 +240,65 @@ func TestClientsOwnLookupHeadersAreRemovedWhileTheSettingIsOff(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequestWaitsAsLongAsTheLookupTimeoutSays(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// The test runs in a synctest bubble, where the time package runs on a
|
||||||
|
// clock of the test's own: the wait lasts exactly as long as it should,
|
||||||
|
// however slowly the test process runs. Nothing in it may wait on the
|
||||||
|
// network, which would keep that clock from moving on: the request is
|
||||||
|
// handed to the proxy's handler, and GeoJS is one that never answers.
|
||||||
|
synctest.Test(t, func(t *testing.T) {
|
||||||
|
// Not the default second. The exclusive list needs the answer, and
|
||||||
|
// the app is never reached.
|
||||||
|
const timeout = 3 * time.Second
|
||||||
|
|
||||||
|
server, out, _ := newProxy(t, "http://app.invalid", unansweredGeoJSURL,
|
||||||
|
time.Now, map[string]string{
|
||||||
|
lookupTimeout: timeout.String(),
|
||||||
|
allowedCountries: "DE",
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/",
|
||||||
|
http.NoBody)
|
||||||
|
req.RemoteAddr = net.JoinHostPort(fromDE, "1234")
|
||||||
|
began := time.Now()
|
||||||
|
|
||||||
|
server.Handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||||
|
|
||||||
|
if waited := time.Since(began); waited != timeout {
|
||||||
|
t.Errorf("the request waited %s for its answer, want %s", waited, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without an answer, the client is in no country the list allows.
|
||||||
|
wantLine(t, out.requestLine(t), http.StatusForbidden,
|
||||||
|
requestlog.ActionCountryDenied)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// unansweredGeoJSURL is where a GeoJS that never answers is asked: a
|
||||||
|
// request to it waits, without the network, until it is abandoned.
|
||||||
|
// TestMain registers it with Go's default transport, through which GeoJS
|
||||||
|
// is asked.
|
||||||
|
const unansweredGeoJSURL = "unanswered://geojs/v1/ip/geo.json"
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
transport, _ := http.DefaultTransport.(*http.Transport)
|
||||||
|
transport.RegisterProtocol("unanswered", unansweredGeoJS{})
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unansweredGeoJS is the GeoJS at unansweredGeoJSURL.
|
||||||
|
type unansweredGeoJS struct{}
|
||||||
|
|
||||||
|
// RoundTrip waits until req is abandoned.
|
||||||
|
func (unansweredGeoJS) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
<-req.Context().Done()
|
||||||
|
|
||||||
|
return nil, req.Context().Err()
|
||||||
|
}
|
||||||
|
|
||||||
// clientsOwnLookupHeaders are the X-Client-ASN and X-Client-Country a
|
// clientsOwnLookupHeaders are the X-Client-ASN and X-Client-Country a
|
||||||
// client sends of its own, each twice, in two cases.
|
// client sends of its own, each twice, in two cases.
|
||||||
const clientsOwnLookupHeaders = "X-Client-ASN: AS1\r\nx-client-asn: AS2\r\n" +
|
const clientsOwnLookupHeaders = "X-Client-ASN: AS1\r\nx-client-asn: AS2\r\n" +
|
||||||
|
|||||||
@@ -235,16 +235,43 @@ func startProxyWithClock(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startProxyWithAlerts is startProxyWithClock, and returns the queue of
|
// startProxyWithAlerts is startProxyWithClock, and returns the queue of
|
||||||
// the alerts the proxy raises as well, as the settings in env make it. No
|
// the alerts the proxy raises as well, as newProxy makes them.
|
||||||
// alert is sent from it: they wait in it, for the test to look at. With
|
|
||||||
// no geojsURL, there is no stand-in for GeoJS to look clients up at, and
|
|
||||||
// SWWAF_LOOKUP_SOURCE is off unless env sets it.
|
|
||||||
func startProxyWithAlerts(
|
func startProxyWithAlerts(
|
||||||
t *testing.T, appURL, geojsURL string, now func() time.Time,
|
t *testing.T, appURL, geojsURL string, now func() time.Time,
|
||||||
env map[string]string,
|
env map[string]string,
|
||||||
) (string, *output, *proxy.Server, *alerts.Queue) {
|
) (string, *output, *proxy.Server, *alerts.Queue) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
server, out, alertQueue := newProxy(t, appURL, geojsURL, now, env)
|
||||||
|
|
||||||
|
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = server.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = server.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
return listener.Addr().String(), out, server, alertQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
// newProxy makes the server startProxyWithClock starts, without starting
|
||||||
|
// it, and returns it, what it writes, and the queue of the alerts the
|
||||||
|
// proxy raises, as the settings in env make it. No alert is sent from the
|
||||||
|
// queue: they wait in it, for the test to look at. With no geojsURL, there
|
||||||
|
// is no stand-in for GeoJS to look clients up at, and SWWAF_LOOKUP_SOURCE
|
||||||
|
// is off unless env sets it.
|
||||||
|
func newProxy(
|
||||||
|
t *testing.T, appURL, geojsURL string, now func() time.Time,
|
||||||
|
env map[string]string,
|
||||||
|
) (*proxy.Server, *output, *alerts.Queue) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
settings := map[string]string{
|
settings := map[string]string{
|
||||||
"SWWAF_UPSTREAM_URL": appURL, rulesDir: t.TempDir(), instanceName: "app",
|
"SWWAF_UPSTREAM_URL": appURL, rulesDir: t.TempDir(), instanceName: "app",
|
||||||
}
|
}
|
||||||
@@ -293,20 +320,7 @@ func startProxyWithAlerts(
|
|||||||
Alerts: alertQueue,
|
Alerts: alertQueue,
|
||||||
})
|
})
|
||||||
|
|
||||||
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
return server, out, alertQueue
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_ = server.Serve(listener)
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = server.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
return listener.Addr().String(), out, server, alertQueue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newClient returns an HTTP client that sends requests as they are made,
|
// newClient returns an HTTP client that sends requests as they are made,
|
||||||
|
|||||||
Reference in New Issue
Block a user