rproxy and servewaf replace the one-process smallwebwaf (closes #172, closes #165)
check / check (push) Failing after 14m44s

`smallwebwaf` with no command now prints the commands and exits with status 2.
The image runs rproxy, or servewaf with SMALLWEBWAF_COMMAND=servewaf. The
passing-on code, its settings and its log fields and metric go. internal/proxy
keeps the checks, answers, reports and own endpoints; its tests run through the
real rproxy. The documents describe the pair.

Judgement call: clients.json keeps token_refused, since state files refuse unknown fields.
Deviation: script/gitea-requests sets SWWAF_LOG_REQUEST_HEADERS, to label requests.
Partially verified: make example-app and make gitea-requests never built the image, as Ubuntu's snapshot service answered 5xx.

Model: opus-5-5
This commit was merged in pull request #184.
This commit is contained in:
2026-11-05 13:15:05 +01:00
parent 17c5dd2438
commit 344ce13c96
71 changed files with 2653 additions and 5493 deletions
+19 -36
View File
@@ -14,19 +14,13 @@ import (
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
)
// The action a request line names: what smallwebwaf did with the
// request.
// The action a request line names: what servewaf did with the request.
const (
// ActionForward is a request passed to the app.
// ActionForward is a request servewaf allowed, which rproxy passes to the
// app.
ActionForward = "forward"
// ActionTooLarge is a request or response over its size limit, or, in
// servewaf, a request over the header limits.
// ActionTooLarge is a request over the header limits.
ActionTooLarge = "too_large"
// ActionTimedOut is a request or response that ran out of time.
ActionTimedOut = "timed_out"
// ActionUpstreamError is a request the app could not be reached
// for, or whose answer could not be passed on.
ActionUpstreamError = "upstream_error"
// ActionRateLimited is a request refused because it took its client
// over a rate limit, which bans the client.
ActionRateLimited = "rate_limited"
@@ -48,8 +42,8 @@ const (
ActionDenied = "denied"
// ActionCountryDenied is a request refused for its client's country.
ActionCountryDenied = "country_denied"
// ActionAdmin is a request smallwebwaf answered at one of its own
// endpoints, under /_smallwebwaf/.
// ActionAdmin is a request servewaf answered at one of its own endpoints,
// or a question about a path under /_smallwebwaf/.
ActionAdmin = "admin"
)
@@ -86,8 +80,8 @@ type Line struct {
// two lines about a question.
ServeWAFLine string `json:"servewaf_line,omitempty"`
// The standard web log fields. Scheme is how the client reached
// smallwebwaf, or the trusted proxy in front of it.
// The standard web log fields. Scheme is how the client reached rproxy, or
// the trusted proxy in front of it.
Time string `json:"time"`
Instance string `json:"instance"`
ClientIP string `json:"client_ip"`
@@ -103,8 +97,8 @@ type Line struct {
Referer string `json:"referer"`
UserAgent string `json:"user_agent"`
// Request detail. RequestID is the X-Request-ID a trusted proxy sent,
// or a new one, and is sent on to the app. ForwardedFor is the
// Request detail. RequestID is the id rproxy made for the request, or for a
// request to servewaf's own endpoints a new one. ForwardedFor is the
// X-Forwarded-For header as received. ClientGroup is the netblock the
// client is counted as. ASN, ASName and Country are the client's AS
// number, AS name and country, as looked up.
@@ -123,17 +117,10 @@ type Line struct {
RequestHeaders map[string]string `json:"request_headers,omitempty"`
HasAuthorization bool `json:"has_authorization,omitempty"`
HasCookie bool `json:"has_cookie,omitempty"`
// Websocket is true when the connection was upgraded, as for a
// WebSocket.
Websocket bool `json:"websocket,omitempty"`
// Response detail, from the headers of the answer: the app's, as
// passed on, or those of smallwebwaf's own. Aborted is true when the
// client went away early.
// Response detail, of servewaf's own answer. Aborted is true when rproxy, or
// the admin's tool, went away before the answer.
ResponseContentType string `json:"response_content_type,omitempty"`
UpstreamStatus int `json:"upstream_status,omitempty"`
CacheControl string `json:"cache_control,omitempty"`
Location string `json:"location,omitempty"`
Aborted bool `json:"aborted,omitempty"`
// The decision.
@@ -183,17 +170,13 @@ type Line struct {
// The timings, in milliseconds. DurationChecks is the time until the
// checks were done, and DurationWAF the part of it the Core Rule Set
// took. DurationUpstreamConnect, DurationUpstreamFirstByte and
// DurationUpstreamTotal run from when the request was handed to the
// app: until there was a connection to it, until the first byte of its
// answer arrived, and until the end. Each but DurationTotal is nil for
// a request that did not get that far.
DurationTotal float64 `json:"duration_total"`
DurationChecks *float64 `json:"duration_checks,omitempty"`
DurationWAF *float64 `json:"duration_waf,omitempty"`
DurationUpstreamConnect *float64 `json:"duration_upstream_connect,omitempty"`
DurationUpstreamFirstByte *float64 `json:"duration_upstream_first_byte,omitempty"`
DurationUpstreamTotal *float64 `json:"duration_upstream_total,omitempty"`
// took. DurationUpstreamTotal is what the report of an allowed request gives,
// from the hand-over to the app until the end of its answer. Each but
// DurationTotal is nil for a request that did not get that far.
DurationTotal float64 `json:"duration_total"`
DurationChecks *float64 `json:"duration_checks,omitempty"`
DurationWAF *float64 `json:"duration_waf,omitempty"`
DurationUpstreamTotal *float64 `json:"duration_upstream_total,omitempty"`
}
// RProxyLine is rproxy's short line in the request log about one request. The