diff --git a/testing/coreruleset/.ftw.yml b/testing/coreruleset/.ftw.yml index 0cc93009b..c2932f4b1 100644 --- a/testing/coreruleset/.ftw.yml +++ b/testing/coreruleset/.ftw.yml @@ -1,13 +1,23 @@ --- testoverride: ignore: - 920100-4: 'Invalid uri, Coraza not reached - 404 page not found' 920100-5: 'Invalid uri, Coraza not reached - 404 page not found' 920100-8: 'Go/http allows a colon in the path. Test expects status 400 or 403 (Apache behaviour)' 920270-4: 'Rule works, log contains 920270. Test expects status 400 (Apache behaviour)' - 920272-5: 'Rule works, log contains 920272. Test expects status 400 (Apache behaviour)' 920290-1: 'Rule works, log contains 920290. Test expects status 400 (Apache behaviour)' 920290-4: 'Go/http returns 400 Bad Request: missing required Host header' 920430-8: 'Go/http does not allow HTTP/3.0 - 505 HTTP Version Not Supported' - 932200-13: 'wip' 930110-7: 'CRS issue: https://github.com/coreruleset/coreruleset/issues/3736' + + # TODO: investigate + 932200-13: 'Failing only in multiphase evalution' + 932300-10: 'Failing only in multiphase evalution' + 933120-2: 'Failing only in multiphase evalution' + 920274-1: '' + 920280-3: '' + 920430-3: '' + 920430-5: '' + 920430-9: '' + 920610-2: '' + 920620-1: '' + diff --git a/testing/coreruleset/albedo_test.go b/testing/coreruleset/albedo_test.go new file mode 100644 index 000000000..e4ff4daca --- /dev/null +++ b/testing/coreruleset/albedo_test.go @@ -0,0 +1,124 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +// These benchmarks don't currently compile with TinyGo +//go:build !tinygo +// +build !tinygo + +// Note: The following code has been extracted from https://github.com/coreruleset/albedo/blob/main/server/server.go +// TODO: Make it possible to import albedo. +package coreruleset + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "strings" + "testing" +) + +type reflectionSpec struct { + Status int `json:"status"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` + EncodedBody string `json:"encodedBody"` + LogMessage string `json:"logMessage"` +} + +func handleReflect(t testing.TB, w http.ResponseWriter, r *http.Request) { + log.Println("Received reflection request") + + body, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("Failed to parse request body")) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } + log.Println("Failed to parse request body") + return + } + spec := &reflectionSpec{} + if err = json.Unmarshal(body, spec); err != nil { + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("Invalid JSON in request body")) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } + log.Println("Invalid JSON in request body") + return + } + + if spec.LogMessage != "" { + log.Println(spec.LogMessage) + } + + for name, value := range spec.Headers { + log.Printf("Reflecting header '%s':'%s'", name, value) + w.Header().Add(name, value) + } + + if spec.Status > 0 && spec.Status < 100 || spec.Status >= 600 { + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte(fmt.Sprintf("Invalid status code: %d", spec.Status))) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } + log.Printf("Invalid status code: %d", spec.Status) + return + } + status := spec.Status + if status == 0 { + status = http.StatusOK + } + log.Printf("Reflecting status '%d'", status) + w.WriteHeader(status) + + responseBody, err := decodeBody(t, spec) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte(err.Error())) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } + log.Println(err.Error()) + return + } + + if responseBody == "" { + return + } + + responseBodyBytes := []byte(responseBody) + if len(responseBody) > 200 { + responseBody = responseBody[:min(len(responseBody), 200)] + "..." + } + log.Printf("Reflecting body '%s'", responseBody) + _, err = w.Write(responseBodyBytes) + if err != nil { + log.Printf("Failed to write response body: %s", err.Error()) + } +} + +func decodeBody(t testing.TB, spec *reflectionSpec) (string, error) { + t.Helper() + if spec.Body != "" { + return spec.Body, nil + } + + if spec.EncodedBody == "" { + return "", nil + } + + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(spec.EncodedBody)) + bodyBytes, err := io.ReadAll(decoder) + if err != nil { + return "", errors.New("invalid base64 encoding of response body") + + } + return string(bodyBytes), nil +} diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go index f926bda2c..eb9249c23 100644 --- a/testing/coreruleset/coreruleset_test.go +++ b/testing/coreruleset/coreruleset_test.go @@ -221,31 +221,12 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \ } s := httptest.NewServer(txhttp.WrapHandler(waf, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Emulates https://github.com/coreruleset/albedo behavior defer r.Body.Close() w.Header().Set("Content-Type", "text/plain") switch { - case r.URL.Path == "/anything", r.URL.Path == "/post": - body, err := io.ReadAll(r.Body) - // Emulated httpbin behaviour: /anything and /post endpoints act as an echo server, writing back the request body - if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { - // Tests 954120-1 and 954120-2 are the only two calling /anything with a POST and payload is urlencoded - if err != nil { - t.Fatalf("handler can not read request body: %v", err) - } - urldecodedBody, err := url.QueryUnescape(string(body)) - if err != nil { - t.Logf("[warning] handler can not unescape urlencoded request body: %v", err) - // If the body can't be unescaped, we will keep going with the received body - urldecodedBody = string(body) - } - fmt.Fprint(w, urldecodedBody) - } else { - _, err = w.Write(body) - if err != nil { - t.Fatalf("handler can not write request body: %v", err) - } - } - + case r.URL.Path == "/reflect": + handleReflect(t, w, r) case strings.HasPrefix(r.URL.Path, "/base64/"): // Emulated httpbin behaviour: /base64 endpoint write the decoded base64 into the response body b64Decoded, err := b64.StdEncoding.DecodeString(strings.TrimPrefix(r.URL.Path, "/base64/")) @@ -254,8 +235,7 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \ } fmt.Fprint(w, string(b64Decoded)) default: - // Common path "/status/200" defaults here - fmt.Fprint(w, "Hello!") + // Albedo return 200 with no body } }))) defer s.Close() @@ -266,7 +246,7 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \ if err != nil { return err } - ftwt, err := test.GetTestFromYaml(yaml) + ftwt, err := test.GetTestFromYaml(yaml, path) if err != nil { return err } @@ -292,16 +272,19 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \ cfg.WithLogfile(errorPath) cfg.TestOverride.Overrides.DestAddr = &host cfg.TestOverride.Overrides.Port = &port - res, err := runner.Run(cfg, tests, runner.RunnerConfig{ ShowTime: false, }, output.NewOutput("quiet", os.Stdout)) if err != nil { t.Fatal(err) } - - if len(res.Stats.Failed) > 0 { - t.Errorf("failed tests: %v", res.Stats.Failed) + totalIgnored := len(res.Stats.Ignored) + if totalIgnored > 0 { + t.Logf("[info] %d ignored tests: %v", totalIgnored, res.Stats.Ignored) + } + totalFailed := len(res.Stats.Failed) + if totalFailed > 0 { + t.Errorf("[fatal] %d failed tests: %v", totalFailed, res.Stats.Failed) } } diff --git a/testing/coreruleset/go.mod b/testing/coreruleset/go.mod index a6c00074d..358ed624f 100644 --- a/testing/coreruleset/go.mod +++ b/testing/coreruleset/go.mod @@ -4,9 +4,9 @@ go 1.21 require ( github.com/bmatcuk/doublestar/v4 v4.6.1 - github.com/corazawaf/coraza-coreruleset/v4 v4.3.0 + github.com/corazawaf/coraza-coreruleset/v4 v4.5.0 github.com/corazawaf/coraza/v3 v3.0.0-00010101000000-000000000000 - github.com/coreruleset/go-ftw v0.6.4 + github.com/coreruleset/go-ftw v1.0.3 github.com/rs/zerolog v1.33.0 ) @@ -15,8 +15,8 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/corazawaf/libinjection-go v0.2.1 // indirect - github.com/coreruleset/ftw-tests-schema v1.1.0 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/coreruleset/ftw-tests-schema/v2 v2.1.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/goccy/go-yaml v1.11.3 // indirect @@ -28,7 +28,7 @@ require ( github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect github.com/knadh/koanf/providers/env v0.1.0 // indirect - github.com/knadh/koanf/providers/file v0.1.0 // indirect + github.com/knadh/koanf/providers/file v1.1.0 // indirect github.com/knadh/koanf/providers/rawbytes v0.1.0 // indirect github.com/knadh/koanf/v2 v2.1.1 // indirect github.com/kyokomi/emoji/v2 v2.2.13 // indirect @@ -46,6 +46,7 @@ require ( golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/binaryregexp v0.2.0 // indirect diff --git a/testing/coreruleset/go.sum b/testing/coreruleset/go.sum index babfd4721..74e72c0b2 100644 --- a/testing/coreruleset/go.sum +++ b/testing/coreruleset/go.sum @@ -6,19 +6,19 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/corazawaf/coraza-coreruleset/v4 v4.3.0 h1:izzVRUxfvVf1OXhRQXpFm1jj3g/cIlLu9SiNzXOW7XU= -github.com/corazawaf/coraza-coreruleset/v4 v4.3.0/go.mod h1:RQMGurig+irQq7v21yq7rM/9SAEf1bT6hCSplJ0ByKY= +github.com/corazawaf/coraza-coreruleset/v4 v4.5.0 h1:4BDr9/yWKSJ7Ch3h7SvSqJBASju73+EqIIF0WxjsFgQ= +github.com/corazawaf/coraza-coreruleset/v4 v4.5.0/go.mod h1:1FQt1p+JSQ6tYrafMqZrEEdDmhq6aVuIJdnk+bM9hMY= github.com/corazawaf/libinjection-go v0.2.1 h1:vNJ7L6c4xkhRgYU6sIO0Tl54TmeCQv/yfxBma30Dy/Y= github.com/corazawaf/libinjection-go v0.2.1/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreruleset/ftw-tests-schema v1.1.0 h1:3+NYrdLE3HVmOc3nGrisRBBvY9lGjePUrV+YkT5Ay3s= -github.com/coreruleset/ftw-tests-schema v1.1.0/go.mod h1:gRd9wBxjUI85HypWRDxJzbk1JqHC4KTxl0l/Y2p9QK4= -github.com/coreruleset/go-ftw v0.6.4 h1:EdDNld38Jv4lxqHS+csGOJuHu1/8rpp4TlrFyoijTPk= -github.com/coreruleset/go-ftw v0.6.4/go.mod h1:IayMjfOmmNNBcqTcZU92e6UZTy79/eFdmJEmRu8tLs4= +github.com/coreruleset/ftw-tests-schema/v2 v2.1.0 h1:2ilKzKRG5UzzxBcrJLXFtPalStdQ9jzzaYFuFk0OEk0= +github.com/coreruleset/ftw-tests-schema/v2 v2.1.0/go.mod h1:ZHVFX5ses4+5IxUP0ufCNg/VqRWxziH6ZuUca092Hxo= +github.com/coreruleset/go-ftw v1.0.3 h1:DVqoTvBGXtAd0knvlyxr7d/bk1xMkzq8cfSbjxeOxLI= +github.com/coreruleset/go-ftw v1.0.3/go.mod h1:NzYL8DIoAbulVVneBlnudFdlJziO3mJU3iIV2fZK46U= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -52,8 +52,8 @@ github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg= github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ= -github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c= -github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA= +github.com/knadh/koanf/providers/file v1.1.0 h1:MTjA+gRrVl1zqgetEAIaXHqYje0XSosxSiMD4/7kz0o= +github.com/knadh/koanf/providers/file v1.1.0/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= @@ -84,8 +84,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -108,6 +108,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=