Rename discriminator to waf everywhere (closes #312)
check / check (push) Failing after 2m11s

The command is now `sww waf`, and the package is `internal/waf`. Types,
functions, files, tests, comments, docs, the Dockerfile, the runit
script, `script/`, compose examples, the log field `waf_line` and the
`X-From-Waf` test header follow. No behaviour changes, and no setting
changes name: rproxy already finds the waf through `SWRP_WAF_BACKEND`.

Done with one `sed -i` (Discriminator and DISCRIMINATOR to WAF,
discriminator to waf) over the files `git grep -i discriminator`
listed, as sneak allowed for this rename, then `git mv` for the
package and files. By hand afterwards: the header's spelling, which
the linter requires, and the padding after `waf` in `sww`'s usage
text.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 01:15:09 +00:00
1 parent ad36dd1775
commit d31a052aff
89 files changed
+1454 -1489

No files matched your search

+53 -53
View File
@@ -19,18 +19,18 @@ import (
"testing"
"time"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
"sneak.berlin/go/smallwebwaf/internal/sww"
"sneak.berlin/go/smallwebwaf/internal/waf"
)
const (
// wafBackend is the setting that names discriminator to rproxy.
// wafBackend is the setting that names waf to rproxy.
wafBackend = "SWRP_WAF_BACKEND"
// rproxyListenAddr is the setting that says where rproxy listens.
rproxyListenAddr = "SWRP_LISTEN_ADDR"
// rproxyUpstreamURL is the setting that says where the app is.
rproxyUpstreamURL = "SWRP_UPSTREAM_URL"
// rproxyTimeout is the setting that says how long rproxy waits for discriminator.
// rproxyTimeout is the setting that says how long rproxy waits for waf.
rproxyTimeout = "SWRP_TIMEOUT_MS"
// bannedProbe is a path the default rule file bans a client for.
bannedProbe = "/.env"
@@ -52,7 +52,7 @@ func runRProxy(ctx context.Context, env map[string]string, out *output) int {
})
}
// startRProxy runs rproxy with discriminator at backend and the app at appURL, and
// startRProxy runs rproxy with waf at backend and the app at appURL, and
// returns its URL and what it writes. It believes X-Forwarded-For from the
// tests. It stops as the test ends, and that it stopped in order is checked.
func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
@@ -91,10 +91,10 @@ func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
return "http://" + addr, out
}
// startDiscriminator runs discriminator with the settings in env, and returns
// startWAF runs waf with the settings in env, and returns
// its address and URL, what it writes, and what stops it, which checks that it
// stopped in order.
func startDiscriminator(t *testing.T, env map[string]string) (string, *output, func()) {
func startWAF(t *testing.T, env map[string]string) (string, *output, func()) {
t.Helper()
ctx, stop := context.WithCancel(t.Context())
@@ -113,17 +113,17 @@ func startDiscriminator(t *testing.T, env map[string]string) (string, *output, f
select {
case status := <-exited:
if status != 0 {
t.Fatalf("discriminator's exit status %d, want 0; output:\n%s", status, out.text())
t.Fatalf("waf's exit status %d, want 0; output:\n%s", status, out.text())
}
case <-time.After(waitLimit):
t.Fatal("discriminator still running after being told to stop")
t.Fatal("waf still running after being told to stop")
}
}
}
// discriminatorSettings are discriminator's settings with the default rule
// wafSettings are waf's settings with the default rule
// file, to listen at any free port.
func discriminatorSettings(t *testing.T) map[string]string {
func wafSettings(t *testing.T) map[string]string {
t.Helper()
return map[string]string{
@@ -136,7 +136,7 @@ func discriminatorSettings(t *testing.T) map[string]string {
func TestABanThroughOneSiteRefusesTheClientOnTheOther(t *testing.T) {
t.Parallel()
addr, discriminatorOut, stop := startDiscriminator(t, discriminatorSettings(t))
addr, wafOut, stop := startWAF(t, wafSettings(t))
defer stop()
backend := "http://" + addr
@@ -151,32 +151,32 @@ func TestABanThroughOneSiteRefusesTheClientOnTheOther(t *testing.T) {
wantStatus(t, second+"/", placed, http.StatusForbidden)
wantStatus(t, second+"/", otherClient, http.StatusOK)
// One request's three lines carry one id, rproxy's with discriminator's answer.
// One request's three lines carry one id, rproxy's with waf's answer.
line := firstOut.line(t, "path", bannedProbe)
if line["disposition"] != discriminator.DispositionDeny ||
if line["disposition"] != waf.DispositionDeny ||
line["client_ip"] != placed {
t.Errorf("rproxy's line is %v", line)
}
id, _ := line["request_id"].(string)
decision := discriminatorLine(t, discriminatorOut, id, "decision")
report := discriminatorLine(t, discriminatorOut, id, "report")
decision := wafLine(t, wafOut, id, "decision")
report := wafLine(t, wafOut, id, "report")
if decision["host"] != line["host"] || decision["client_ip"] != placed ||
report["status"] != float64(http.StatusForbidden) {
t.Errorf("discriminator's lines are %v and %v", decision, report)
t.Errorf("waf's lines are %v and %v", decision, report)
}
}
// discriminatorLine waits for discriminator's line with request id and
// discriminator_line kind.
func discriminatorLine(t *testing.T, out *output, id, kind string) map[string]any {
// wafLine waits for waf's line with request id and
// waf_line kind.
func wafLine(t *testing.T, out *output, id, kind string) map[string]any {
t.Helper()
deadline := time.Now().Add(waitLimit)
for time.Now().Before(deadline) {
line := out.findLine(t, func(fields map[string]any) bool {
return fields["request_id"] == id && fields["discriminator_line"] == kind
return fields["request_id"] == id && fields["waf_line"] == kind
})
if line != nil {
return line
@@ -190,14 +190,14 @@ func discriminatorLine(t *testing.T, out *output, id, kind string) map[string]an
return nil
}
func TestTheSiteAnswersWhileDiscriminatorIsStoppedAndStartedAgain(t *testing.T) {
func TestTheSiteAnswersWhileWAFIsStoppedAndStartedAgain(t *testing.T) {
t.Parallel()
env := discriminatorSettings(t)
addr, _, stop := startDiscriminator(t, env)
env := wafSettings(t)
addr, _, stop := startWAF(t, env)
// rproxy asks discriminator through an address the test holds throughout,
// so no other test can take it while discriminator is stopped.
// rproxy asks waf through an address the test holds throughout,
// so no other test can take it while waf is stopped.
backend, forwardTo := startForwarder(t)
forwardTo(addr)
@@ -228,22 +228,22 @@ func TestTheSiteAnswersWhileDiscriminatorIsStoppedAndStartedAgain(t *testing.T)
wantStatus(t, site+bannedProbe, placed, http.StatusForbidden)
wantStatus(t, site+"/", placed, http.StatusForbidden)
// rproxy's connections are closed at once from here, before discriminator
// rproxy's connections are closed at once from here, before waf
// stops and frees its port.
forwardTo("")
stop()
// With discriminator down, the site answers, and rproxy says the default did it.
// With waf down, the site answers, and rproxy says the default did it.
wantStatus(t, site+"/after-stop", placed, http.StatusOK)
line := out.line(t, "path", "/after-stop")
if line["disposition"] != discriminator.DispositionAllow ||
if line["disposition"] != waf.DispositionAllow ||
line["default_disposition"] != true {
t.Errorf("rproxy's line is %v", line)
}
// Started again with its state files, it applies the ban again.
addr, _, stop = startDiscriminator(t, env)
addr, _, stop = startWAF(t, env)
forwardTo(addr)
defer func() {
@@ -397,10 +397,10 @@ func sendRaw(t *testing.T, site, request string) int {
return res.StatusCode
}
func TestAChunkedBodyStaysABodyAtDiscriminator(t *testing.T) {
func TestAChunkedBodyStaysABodyAtWAF(t *testing.T) {
t.Parallel()
addr, discriminatorOut, stop := startDiscriminator(t, discriminatorSettings(t))
addr, wafOut, stop := startWAF(t, wafSettings(t))
defer stop()
site, _ := startRProxy(t, "http://"+addr, startApp(t))
@@ -409,7 +409,7 @@ func TestAChunkedBodyStaysABodyAtDiscriminator(t *testing.T) {
const smuggledID = "smuggled-request"
smuggled := "GET " + bannedProbe + " HTTP/1.1\r\nHost: site.example\r\n" +
discriminator.ClientHeader + ": " + otherClient + "\r\n" +
waf.ClientHeader + ": " + otherClient + "\r\n" +
"X-Request-ID: " + smuggledID + "\r\n\r\n"
status := sendRaw(t, site, "POST /upload HTTP/1.1\r\nHost: site.example\r\n"+
@@ -425,18 +425,18 @@ func TestAChunkedBodyStaysABodyAtDiscriminator(t *testing.T) {
time.Sleep(pollInterval)
}
line := discriminatorOut.findLine(t, func(fields map[string]any) bool {
line := wafOut.findLine(t, func(fields map[string]any) bool {
return fields["request_id"] == smuggledID
})
if line != nil {
t.Errorf("discriminator read the body as a request: %v", line)
t.Errorf("waf read the body as a request: %v", line)
}
}
func TestATargetThatCannotGoToDiscriminatorIsRefusedForABannedClient(t *testing.T) {
func TestATargetThatCannotGoToWAFIsRefusedForABannedClient(t *testing.T) {
t.Parallel()
addr, _, stop := startDiscriminator(t, discriminatorSettings(t))
addr, _, stop := startWAF(t, wafSettings(t))
defer stop()
site, out := startRProxy(t, "http://"+addr, startApp(t))
@@ -460,7 +460,7 @@ func TestAClientWithAZoneIsTheClientWithout(t *testing.T) {
const zoned = "2001:db8::1%eth0"
addr, _, stop := startDiscriminator(t, discriminatorSettings(t))
addr, _, stop := startWAF(t, wafSettings(t))
defer stop()
site, out := startRProxy(t, "http://"+addr, startApp(t))
@@ -475,7 +475,7 @@ func TestAClientWithAZoneIsTheClientWithout(t *testing.T) {
}
}
func TestAStatusDiscriminatorDoesNotTakeIsReportedAs599(t *testing.T) {
func TestAStatusWAFDoesNotTakeIsReportedAs599(t *testing.T) {
t.Parallel()
const status = 799
@@ -486,7 +486,7 @@ func TestAStatusDiscriminatorDoesNotTakeIsReportedAs599(t *testing.T) {
}))
t.Cleanup(app.Close)
addr, discriminatorOut, stop := startDiscriminator(t, discriminatorSettings(t))
addr, wafOut, stop := startWAF(t, wafSettings(t))
defer stop()
site, out := startRProxy(t, "http://"+addr, app.URL)
@@ -500,9 +500,9 @@ func TestAStatusDiscriminatorDoesNotTakeIsReportedAs599(t *testing.T) {
id, _ := line["request_id"].(string)
report := discriminatorLine(t, discriminatorOut, id, "report")
report := wafLine(t, wafOut, id, "report")
if report["status"] != 599.0 {
t.Errorf("discriminator's line is %v, want status 599", report)
t.Errorf("waf's line is %v, want status 599", report)
}
}
@@ -544,16 +544,16 @@ func startRProxyProcess(t *testing.T, env ...string) (string, *exec.Cmd, *output
return addr, cmd, out
}
func TestRProxyAsksADiscriminatorThatSpeaksHTTP2OverHTTP1(t *testing.T) {
func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
t.Parallel()
protocols := make(chan string, 10)
backend := httptest.NewUnstartedServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(discriminator.ClientHeader) != "" {
if r.Header.Get(waf.ClientHeader) != "" {
protocols <- r.Proto
w.Header().Set(discriminator.DispositionHeader, discriminator.DispositionDeny)
w.Header().Set(waf.DispositionHeader, waf.DispositionDeny)
w.WriteHeader(http.StatusForbidden)
}
}))
@@ -563,7 +563,7 @@ func TestRProxyAsksADiscriminatorThatSpeaksHTTP2OverHTTP1(t *testing.T) {
// rproxy has no setting for the certificates it trusts, so it is told of this
// one as Go's client is told of any, in a process of its own.
certificates := filepath.Join(t.TempDir(), "discriminator.pem")
certificates := filepath.Join(t.TempDir(), "waf.pem")
err := os.WriteFile(certificates, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: backend.Certificate().Raw,
@@ -585,7 +585,7 @@ func TestRProxyAsksADiscriminatorThatSpeaksHTTP2OverHTTP1(t *testing.T) {
} {
status := sendRaw(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n"+headers+"\r\n")
if status != http.StatusForbidden {
t.Errorf("with %s: status %d, want discriminator's 403", name, status)
t.Errorf("with %s: status %d, want waf's 403", name, status)
}
select {
@@ -594,7 +594,7 @@ func TestRProxyAsksADiscriminatorThatSpeaksHTTP2OverHTTP1(t *testing.T) {
t.Errorf("with %s: asked over %s, want HTTP/1.1", name, protocol)
}
case <-time.After(waitLimit):
t.Errorf("with %s: discriminator was not asked", name)
t.Errorf("with %s: waf was not asked", name)
}
}
}
@@ -623,8 +623,8 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
release := make(chan struct{})
backend := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(discriminator.ClientHeader) != "" {
w.Header().Set(discriminator.DispositionHeader, discriminator.DispositionAllow)
if r.Header.Get(waf.ClientHeader) != "" {
w.Header().Set(waf.DispositionHeader, waf.DispositionAllow)
return
}
@@ -731,7 +731,7 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) {
backend := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(discriminator.DispositionHeader, discriminator.DispositionAllow)
w.Header().Set(waf.DispositionHeader, waf.DispositionAllow)
}))
t.Cleanup(backend.Close)
@@ -794,7 +794,7 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
{"no backend", wafBackend, nil},
{
"another invalid", rproxyTimeout,
map[string]string{wafBackend: "http://discriminator:8080", rproxyTimeout: "soon"},
map[string]string{wafBackend: "http://waf:8080", rproxyTimeout: "soon"},
},
} {
t.Run(tc.name, func(t *testing.T) {
@@ -831,7 +831,7 @@ func TestRProxyAddressInUseStopsTheStart(t *testing.T) {
out := &output{}
status := runRProxy(t.Context(), map[string]string{
wafBackend: "http://discriminator:8080",
wafBackend: "http://waf:8080",
rproxyListenAddr: taken.Addr().String(),
}, out)
if status != 1 {
@@ -848,7 +848,7 @@ func TestMainRunsRProxy(t *testing.T) {
addr, cmd, out := startRProxyProcess(t, wafBackend+"=http://127.0.0.1:9")
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet,
"http://"+addr+discriminator.HealthPath, http.NoBody)
"http://"+addr+waf.HealthPath, http.NoBody)
if err != nil {
t.Fatalf("new request: %v", err)
}