Serve Prometheus metrics behind SWWAF_METRICS_TOKEN (closes #23)
check / check (push) Successful in 3m21s

GET /_smallwebwaf/metrics answers in the Prometheus text format for a
request carrying SWWAF_METRICS_TOKEN, 401 without it and 404 while it is
unset. Every request under /_smallwebwaf/ but the health check now goes
through the checks and is answered where it would be forwarded, 404 for
any path but the metrics, so none reaches the app. In the client's
history a 401 counts as refused, the metrics and the 404s as neither.
SWWAF_METRICS_TOP_N bounds the series by country, the rest counted as
other.

Deviation: go.mod and go.sum written by hand, as go runs only through
make.
Deviation: no metrics yet for state files read again after an edit or
edits set aside; that work is not merged.

Model: opus-5-5
This commit was merged in pull request #76.
This commit is contained in:
2026-10-06 11:40:27 +02:00
parent 68f687cb0c
commit 234c5eac60
25 changed files with 1548 additions and 83 deletions
+26 -17
View File
@@ -28,7 +28,9 @@ func TestRequestTimeouts(t *testing.T) {
for _, tc := range []struct {
name string
env map[string]string
// limit is the setting set to shortTimeout, which runs out; long
// is one set to longTimeoutSetting, which does not, or "".
limit, long string
// appTakesNothing has the app never read, while the client sends
// as fast as it can; otherwise the app reads, and the client
// stops sending halfway.
@@ -36,30 +38,26 @@ func TestRequestTimeouts(t *testing.T) {
want int
}{
{
name: "client request timeout, waiting on the client",
env: map[string]string{clientRequestTimeout: shortTimeoutSetting},
want: http.StatusRequestTimeout,
name: "client request timeout, waiting on the client",
limit: clientRequestTimeout,
want: http.StatusRequestTimeout,
},
{
name: "upstream request timeout, waiting on the client",
env: map[string]string{
upstreamRequestTimeout: shortTimeoutSetting,
clientRequestTimeout: longTimeoutSetting,
},
want: http.StatusRequestTimeout,
name: "upstream request timeout, waiting on the client",
limit: upstreamRequestTimeout,
long: clientRequestTimeout,
want: http.StatusRequestTimeout,
},
{
name: "upstream request timeout, waiting on the app",
env: map[string]string{upstreamRequestTimeout: shortTimeoutSetting},
limit: upstreamRequestTimeout,
appTakesNothing: true,
want: http.StatusGatewayTimeout,
},
{
name: "client request timeout, waiting on the app",
env: map[string]string{
clientRequestTimeout: shortTimeoutSetting,
upstreamRequestTimeout: longTimeoutSetting,
},
name: "client request timeout, waiting on the app",
limit: clientRequestTimeout,
long: upstreamRequestTimeout,
appTakesNothing: true,
want: http.StatusGatewayTimeout,
},
@@ -84,7 +82,12 @@ func TestRequestTimeouts(t *testing.T) {
appURL, sendRequest = app.URL, sendPartOfBody
}
addr, out := startProxy(t, appURL, tc.env)
env := map[string]string{tc.limit: shortTimeoutSetting, metricsToken: token}
if tc.long != "" {
env[tc.long] = longTimeoutSetting
}
addr, out := startProxy(t, appURL, env)
start := time.Now()
got := readResponse(t, sendRequest(t, addr))
wantTimedOut(t, start)
@@ -105,6 +108,7 @@ func TestRequestTimeouts(t *testing.T) {
wantStatus(t, got, want)
wantLine(t, out.requestLine(t), want, requestlog.ActionTimedOut)
wantLimitHits(t, addr, tc.limit, 1)
})
}
}
@@ -198,6 +202,7 @@ func TestAppTooSlowToAnswer(t *testing.T) {
})
addr, out := startProxy(t, app.URL, map[string]string{
upstreamResponseTimeout: shortTimeoutSetting,
metricsToken: token,
})
start := time.Now()
@@ -213,6 +218,8 @@ func TestAppTooSlowToAnswer(t *testing.T) {
t.Errorf("log line has upstream_status %v for an app that never answered",
line.fields["upstream_status"])
}
wantLimitHits(t, addr, upstreamResponseTimeout, 1)
}
func TestAppTooSlowToFinishItsAnswer(t *testing.T) {
@@ -261,6 +268,7 @@ func TestClientTooSlowToTakeTheAnswer(t *testing.T) {
})
addr, out := startProxy(t, app.URL, map[string]string{
clientResponseTimeout: shortTimeoutSetting,
metricsToken: token,
})
start := time.Now()
@@ -272,6 +280,7 @@ func TestClientTooSlowToTakeTheAnswer(t *testing.T) {
line := out.requestLine(t)
wantTimedOut(t, start)
wantLine(t, line, http.StatusOK, requestlog.ActionTimedOut)
wantLimitHits(t, addr, clientResponseTimeout, 1)
}
func TestClosesAnIdleConnection(t *testing.T) {