rproxy, the proxy in front of each app, asks servewaf and fails open (closes #171)
check / check (push) Successful in 7m25s
check / check (push) Successful in 7m25s
`smallwebwaf rproxy` reads the first 64 KiB of each body, asks servewaf over HTTP/1.1, and passes what servewaf allows to the app within its limits. Any other answer goes to the visitor as servewaf gave it. SWRP_DEFAULT_DISPOSITION decides only when servewaf cannot be connected to, does not answer at all, or does not answer within SWRP_TIMEOUT_MS. A method SPEC.md does not list gets 405. Duplication: internal/rproxy copies passing-on code from internal/proxy until #172 removes the originals. Rule suppressed: gosec G704 and G705; gosec at internal/smallwebwaf/rproxy_test.go:431; tagliatelle at internal/requestlog/requestlog.go:203; paralleltest on one test. Judgement call: an answer over 1 MiB, or with a status below 200, gets 502. Judgement call: every target not starting with one / gets 400. Model: opus-5-5
This commit was merged in pull request #181.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Package requestlog writes the lines smallwebwaf prints on stdout: one
|
||||
// JSON object per request, or two from servewaf, marked "type":"request", and
|
||||
// the process's own messages as JSON lines marked "type":"process".
|
||||
// JSON object per request, two from servewaf and a short one from rproxy, marked
|
||||
// "type":"request", and the process's own messages as JSON lines marked
|
||||
// "type":"process".
|
||||
package requestlog
|
||||
|
||||
import (
|
||||
@@ -195,10 +196,51 @@ type Line struct {
|
||||
DurationUpstreamTotal *float64 `json:"duration_upstream_total,omitempty"`
|
||||
}
|
||||
|
||||
// RProxyLine is rproxy's short line in the request log about one request. The
|
||||
// field names, and their order, are those of docs/request-log.md. A field that
|
||||
// does not apply to a request is left out of its line.
|
||||
//
|
||||
//nolint:tagliatelle // docs/request-log.md names the fields in snake_case
|
||||
type RProxyLine struct {
|
||||
Type string `json:"type"`
|
||||
Time string `json:"time"`
|
||||
RequestID string `json:"request_id"`
|
||||
ClientIP string `json:"client_ip"`
|
||||
Host string `json:"host"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
Status int `json:"status"`
|
||||
RequestBytes int64 `json:"request_bytes"`
|
||||
ResponseBytes int64 `json:"response_bytes"`
|
||||
// DurationRequest runs in milliseconds from the request's headers being read
|
||||
// until the app has been sent the whole request, or the answer has started
|
||||
// if that is sooner. DurationResponse runs from then until the answer ends.
|
||||
DurationRequest float64 `json:"duration_request"`
|
||||
DurationResponse float64 `json:"duration_response"`
|
||||
// Disposition is the valid disposition servewaf answered or, while
|
||||
// DefaultDisposition is true, the value of SWRP_DEFAULT_DISPOSITION. It is ""
|
||||
// when neither gave one.
|
||||
Disposition string `json:"disposition,omitempty"`
|
||||
DefaultDisposition bool `json:"default_disposition,omitempty"`
|
||||
}
|
||||
|
||||
// Write writes line to w as one JSON line marked "type":"request".
|
||||
func Write(w io.Writer, line *Line) error {
|
||||
line.Type = "request"
|
||||
|
||||
return writeJSONLine(w, line)
|
||||
}
|
||||
|
||||
// WriteRProxyLine writes line to w as one JSON line marked "type":"request".
|
||||
func WriteRProxyLine(w io.Writer, line *RProxyLine) error {
|
||||
line.Type = "request"
|
||||
|
||||
return writeJSONLine(w, line)
|
||||
}
|
||||
|
||||
// writeJSONLine writes line to w as one JSON line.
|
||||
func writeJSONLine(w io.Writer, line any) error {
|
||||
encoded, err := json.Marshal(line)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode the request log line: %w", err)
|
||||
|
||||
@@ -67,6 +67,28 @@ func TestWriteWritesOneJSONLineMarkedRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteRProxyLineWritesOneJSONLineWithOnlyItsFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
err := requestlog.WriteRProxyLine(&out, &requestlog.RProxyLine{
|
||||
RequestID: "an-id",
|
||||
Disposition: "allow",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
const want = `{"type":"request","time":"","request_id":"an-id","client_ip":"",` +
|
||||
`"host":"","method":"","path":"","query":"","status":0,"request_bytes":0,` +
|
||||
`"response_bytes":0,"duration_request":0,"duration_response":0,` +
|
||||
`"disposition":"allow"}` + "\n"
|
||||
if out.String() != want {
|
||||
t.Errorf("wrote %s, want %s", out.String(), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user