diff --git a/.gitignore b/.gitignore
index 49ec3c1..3b9eaa7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@
# vendor/
# Generated files
-types/types_doc.go
+**/*_doc.go
diff --git a/cmd/generate-doc-yaml-schema/main.go b/cmd/generate-doc-yaml-schema/main.go
index a6e74e2..d02556f 100644
--- a/cmd/generate-doc-yaml-schema/main.go
+++ b/cmd/generate-doc-yaml-schema/main.go
@@ -1,4 +1,4 @@
-// Copyright 2023 Felipe Zipitria
+// Copyright 2023 OWASP CRS
// SPDX-License-Identifier: Apache-2.0
package main
@@ -6,11 +6,21 @@ package main
import (
"os"
- "github.com/coreruleset/ftw-tests-schema/types"
+ overrides "github.com/coreruleset/ftw-tests-schema/types/overrides"
+ test "github.com/coreruleset/ftw-tests-schema/types/test"
)
func main() {
- data, err := types.GetFTWTestDoc().Encode()
+ data, err := test.GetFTWTestDoc().Encode()
+ if err != nil {
+ panic(err)
+ }
+ _, err = os.Stdout.Write(data)
+ if err != nil {
+ panic(err)
+ }
+
+ data, err = overrides.GetFTWOverridesDoc().Encode()
if err != nil {
panic(err)
}
diff --git a/go.mod b/go.mod
index 3cc4b1e..fa04b94 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,11 @@ go 1.21
require github.com/magefile/mage v1.15.0
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+)
+
require (
// Goccy verion is expected to stay at 1.9.2 (Same of go-ftw) because of https://github.com/goccy/go-yaml/issues/325
github.com/goccy/go-yaml v1.9.2
@@ -14,6 +19,7 @@ require (
github.com/fatih/color v1.10.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
+ github.com/stretchr/testify v1.8.4
golang.org/x/sys v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index e5f645a..b3697e2 100644
--- a/go.sum
+++ b/go.sum
@@ -26,8 +26,8 @@ github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH1
github.com/projectdiscovery/yamldoc-go v1.0.4/go.mod h1:8PIPRcUD55UbtQdcfFR1hpIGRWG0P7alClXNGt1TBik=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
diff --git a/mage.go b/mage.go
index 580ba72..3144464 100644
--- a/mage.go
+++ b/mage.go
@@ -1,4 +1,4 @@
-// Copyright 2022 Juan Pablo Tosso and the OWASP Coraza contributors
+// Copyright 2023 CRS
// SPDX-License-Identifier: Apache-2.0
//go:build ignore
diff --git a/magefile.go b/magefile.go
index 399bb52..acc8ede 100644
--- a/magefile.go
+++ b/magefile.go
@@ -1,4 +1,4 @@
-// Copyright 2023 Felipe Zipitria
+// Copyright 2023 CRS
// SPDX-License-Identifier: Apache-2.0
//go:build mage
@@ -16,18 +16,24 @@ import (
)
var addLicenseVersion = "v1.1.1" // https://github.com/google/addlicense
-var golangCILintVer = "v1.55.1" // https://github.com/golangci/golangci-lint/releases
+var golangCILintVer = "v1.55.2" // https://github.com/golangci/golangci-lint/releases
var gosImportsVer = "v0.3.8" // https://github.com/rinchsan/gosimports/releases/tag/v0.3.8
var errRunGoModTidy = errors.New("go.mod/sum not formatted, commit changes")
var errNoGitDir = errors.New("no .git directory found")
var errUpdateGeneratedFiles = errors.New("generated files need to be updated")
-// Format formats code in this repository.
-func Format() error {
+// Generate Go documernation files for YAML structures
+func Generate() error {
if err := sh.RunV("go", "generate", "./..."); err != nil {
return err
}
+ return nil
+}
+
+// Format formats code in this repository.
+func Format() error {
+ mg.SerialDeps(Generate)
if err := sh.RunV("go", "mod", "tidy"); err != nil {
return err
@@ -36,7 +42,7 @@ func Format() error {
// addlicense strangely logs skipped files to stderr despite not being erroneous, so use the long sh.Exec form to
// discard stderr too.
if _, err := sh.Exec(map[string]string{}, io.Discard, io.Discard, "go", "run", fmt.Sprintf("github.com/google/addlicense@%s", addLicenseVersion),
- "-c", "Felipe Zipitria",
+ "-c", "OWASP CRS",
"-s=only",
"-ignore", "**/*.yml",
"-ignore", "**/*.yaml",
@@ -52,9 +58,7 @@ func Format() error {
// Lint verifies code quality.
func Lint() error {
- if err := sh.RunV("go", "generate", "./..."); err != nil {
- return err
- }
+ mg.SerialDeps(Generate)
if sh.Run("git", "diff", "--exit-code", "--", "'*_doc.go'") != nil {
return errUpdateGeneratedFiles
@@ -77,6 +81,8 @@ func Lint() error {
// Test runs all tests.
func Test() error {
+ mg.SerialDeps(Generate)
+
if err := sh.RunV("go", "test", "./..."); err != nil {
return err
}
@@ -84,7 +90,10 @@ func Test() error {
return nil
}
+// Generate Markdown output (printed to terminal)
func Markdown() error {
+ mg.SerialDeps(Generate)
+
if err := sh.RunV("go", "build", "./cmd/generate-doc-yaml-schema"); err != nil {
return err
}
diff --git a/spec/v1.1/ftw.md b/spec/v1.1/ftw.md
new file mode 100644
index 0000000..c4fa341
--- /dev/null
+++ b/spec/v1.1/ftw.md
@@ -0,0 +1,1991 @@
+
+
+
+
+## FTWTest
+Welcome to the FTW YAMLFormat documentation.
+ In this document we will explain all the possible options that can be used within the YAML format.
+ Generally this is the preferred format for writing tests in as they don't require any programming skills
+ in order to understand and change. If you find a bug in this format please open an issue.
+
+
+ FTWTest is the base type used when unmarshaling YAML tests files
+
+
+
+
+
+
+
+
+
+
+
+Meta describes the metadata information of this yaml test file
+
+
+
+
+
+
+
+
+Tests is a list of FTW tests
+
+
+
+Examples:
+
+
+```yaml
+tests:
+ - test_title: 123456-1
+ rule_id: 0
+ test_id: 0
+ desc: Unix RCE using `time`
+ stages:
+ - description: Get cookie from server
+ input:
+ dest_addr: 192.168.0.1
+ port: 8080
+ protocol: http
+ uri: /test
+ version: HTTP/1.1
+ method: REPORT
+ headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+ save_cookie: false
+ stop_magic: true
+ autocomplete_headers: false
+ encoded_request: TXkgRGF0YQo=
+ output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## FTWTestMeta
+
+Appears in:
+
+
+- FTWTest.meta
+
+
+
+
+
+
+
+
+
+author
string
+
+
+
+
+Author is the list of authors that added content to this file
+
+
+
+Examples:
+
+
+```yaml
+# Author
+author: Felipe Zipitria
+```
+
+
+
+
+
+
+
+
+enabled
bool
+
+
+
+
+Enabled indicates if the tests are enabled to be run by the engine or not.
+
+
+
+Examples:
+
+
+```yaml
+# Enabled
+enabled: false
+```
+
+
+
+
+
+
+
+
+name
string
+
+
+
+
+Name is the name of the tests contained in this file.
+
+
+
+Examples:
+
+
+```yaml
+# Name
+name: test01
+```
+
+
+
+
+
+
+
+
+description
string
+
+
+
+
+Description is a textual description of the tests contained in this file.
+
+
+
+Examples:
+
+
+```yaml
+# Description
+description: The tests here target SQL injection.
+```
+
+
+
+
+
+
+
+
+version
string
+
+
+
+
+Version is the version of the YAML Schema.
+
+
+
+Examples:
+
+
+```yaml
+# Version
+version: v1
+```
+
+
+
+
+
+
+
+
+
+
+## Test
+
+Appears in:
+
+
+- FTWTest.tests
+
+
+```yaml
+- test_title: 123456-1
+ rule_id: 0
+ test_id: 0
+ desc: Unix RCE using `time`
+ stages:
+ - description: Get cookie from server
+ input:
+ dest_addr: 192.168.0.1
+ port: 8080
+ protocol: http
+ uri: /test
+ version: HTTP/1.1
+ method: REPORT
+ headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+ save_cookie: false
+ stop_magic: true
+ autocomplete_headers: false
+ encoded_request: TXkgRGF0YQo=
+ output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+test_title
string
+
+
+
+
+TestTitle is the title of this particular test. It is used for inclusion/exclusion of each run by the tool.
+
+
+
+Examples:
+
+
+```yaml
+test_title: 123456-1
+```
+
+
+
+
+
+
+
+
+rule_id
int
+
+
+
+
+RuleId is the ID of the rule this test targets
+
+
+
+Examples:
+
+
+```yaml
+# RuleId
+rule_id: 123456
+```
+
+
+
+
+
+
+
+
+test_id
int
+
+
+
+
+TestId is the ID of the test, in relation to `rule_id`
+
+
+
+Examples:
+
+
+```yaml
+# TestId
+test_id: 4
+```
+
+
+
+
+
+
+
+
+desc
string
+
+
+
+
+TestDescription is the description for this particular test. Should be used to describe the internals of
+the specific things this test is targeting.
+
+
+
+Examples:
+
+
+```yaml
+desc: Unix RCE using `time`
+```
+
+
+
+
+
+
+
+
+
+Stages is the list of all the stages to perform this test.
+
+
+
+Examples:
+
+
+```yaml
+stages:
+ - description: Get cookie from server
+ input:
+ dest_addr: 192.168.0.1
+ port: 8080
+ protocol: http
+ uri: /test
+ version: HTTP/1.1
+ method: REPORT
+ headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+ save_cookie: false
+ stop_magic: true
+ autocomplete_headers: false
+ encoded_request: TXkgRGF0YQo=
+ output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## Stage
+
+Appears in:
+
+
+- Test.stages
+
+
+```yaml
+- description: Get cookie from server
+ input:
+ dest_addr: 192.168.0.1
+ port: 8080
+ protocol: http
+ uri: /test
+ version: HTTP/1.1
+ method: REPORT
+ headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+ save_cookie: false
+ stop_magic: true
+ autocomplete_headers: false
+ encoded_request: TXkgRGF0YQo=
+ output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+StageData is an individual test stage.
+
+
+
+
+
+
+
+description
string
+
+
+
+
+Describes the purpose of this stage.
+
+
+
+Examples:
+
+
+```yaml
+description: Get cookie from server
+```
+
+
+
+
+
+
+
+
+
+Input is the data that is passed to the test
+
+
+
+Examples:
+
+
+```yaml
+# Input
+input:
+ dest_addr: 192.168.0.1
+ port: 8080
+ protocol: http
+ uri: /test
+ version: HTTP/1.1
+ method: REPORT
+ headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+ save_cookie: false
+ stop_magic: true
+ autocomplete_headers: false
+ encoded_request: TXkgRGF0YQo=
+```
+
+
+
+
+
+
+
+
+
+Output is the data that is returned from the test
+
+
+
+Examples:
+
+
+```yaml
+# Output
+output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## StageData
+
+Appears in:
+
+
+- Stage.stage
+
+
+
+
+
+
+
+
+
+
+Input is the data that is passed to the test
+
+
+
+Examples:
+
+
+```yaml
+# Input
+input:
+ dest_addr: 192.168.0.1
+ port: 8080
+ protocol: http
+ uri: /test
+ version: HTTP/1.1
+ method: REPORT
+ headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+ save_cookie: false
+ stop_magic: true
+ autocomplete_headers: false
+ encoded_request: TXkgRGF0YQo=
+```
+
+
+
+
+
+
+
+
+
+Output is the data that is returned from the test
+
+
+
+Examples:
+
+
+```yaml
+# Output
+output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## Input
+
+Appears in:
+
+
+- Stage.input
+
+- StageData.input
+
+
+```yaml
+# Input
+dest_addr: 192.168.0.1
+port: 8080
+protocol: http
+uri: /test
+version: HTTP/1.1
+method: REPORT
+headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+save_cookie: false
+stop_magic: true
+autocomplete_headers: false
+encoded_request: TXkgRGF0YQo=
+```
+```yaml
+# Input
+dest_addr: 192.168.0.1
+port: 8080
+protocol: http
+uri: /test
+version: HTTP/1.1
+method: REPORT
+headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+save_cookie: false
+stop_magic: true
+autocomplete_headers: false
+encoded_request: TXkgRGF0YQo=
+```
+
+
+
+
+
+
+
+dest_addr
string
+
+
+
+
+DestAddr is the IP of the destination host that the test will send the message to.
+
+
+
+Examples:
+
+
+```yaml
+# DestAddr
+dest_addr: 127.0.0.1
+```
+
+
+
+
+
+
+
+
+port
int
+
+
+
+
+Port allows you to declare which port on the destination host the test should connect to.
+
+
+
+Examples:
+
+
+```yaml
+# Port
+port: 80
+```
+
+
+
+
+
+
+
+
+protocol
string
+
+
+
+
+Protocol allows you to declare which protocol the test should use when sending the request.
+
+
+
+Examples:
+
+
+```yaml
+# Protocol
+protocol: http
+```
+
+
+
+
+
+
+
+
+uri
string
+
+
+
+
+URI allows you to declare the URI the test should use as part of the request line.
+
+
+
+Examples:
+
+
+```yaml
+# URI
+uri: /get?hello=world
+```
+
+
+
+
+
+
+
+
+version
string
+
+
+
+
+Version allows you to declare the HTTP version the test should use as part of the request line.
+
+
+
+Examples:
+
+
+```yaml
+# Version
+version: "1.1"
+```
+
+
+
+
+
+
+
+
+method
string
+
+
+
+
+Method allows you to declare the HTTP method the test should use as part of the request line.
+
+
+
+Examples:
+
+
+```yaml
+# Method
+method: GET
+```
+
+
+
+
+
+
+
+
+headers
map[string]string
+
+
+
+
+Method allows you to declare headers that the test should send.
+
+
+
+Examples:
+
+
+```yaml
+# Headers
+headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+```
+
+
+
+
+
+
+
+
+data
string
+
+
+
+
+Data allows you to declare the payload that the test should in the request body.
+
+
+
+Examples:
+
+
+```yaml
+# Data
+data: Bibitti bopi
+```
+
+
+
+
+
+
+
+
+save_cookie
bool
+
+
+
+
+SaveCookie allows you to automatically provide cookies if there are multiple stages and save cookie is set
+
+
+
+Examples:
+
+
+```yaml
+# SaveCookie
+save_cookie: 80
+```
+
+
+
+
+
+
+
+
+stop_magic
bool
+
+
+
+
+StopMagic is deprecated.
+
+
+
+Examples:
+
+
+```yaml
+# StopMagic
+stop_magic: false
+```
+
+
+
+
+
+
+
+
+autocomplete_headers
bool
+
+
+
+
+AutocompleteHeaders allows the test framework to automatically fill the request with Content-Type and Connection headers.
+Defaults to true.
+
+
+
+Examples:
+
+
+```yaml
+# StopMagic
+autocomplete_headers: false
+```
+
+
+
+
+
+
+
+
+encoded_request
string
+
+
+
+
+EncodedRequest will take a base64 encoded string that will be decoded and sent through as the request.
+It will override all other settings
+
+
+
+Examples:
+
+
+```yaml
+# EncodedRequest
+encoded_request: a
+```
+
+
+
+
+
+
+
+
+raw_request
string
+
+
+
+
+RAWRequest is deprecated.
+
+
+
+Examples:
+
+
+```yaml
+# RAWRequest
+raw_request: TXkgRGF0YQo=
+```
+
+
+
+
+
+
+
+
+
+
+## Output
+
+Appears in:
+
+
+- Stage.output
+
+- StageData.output
+
+
+```yaml
+# Output
+status: 200
+log_contains: nothing
+log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+expect_error: true
+```
+```yaml
+# Output
+status: 200
+log_contains: nothing
+log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+expect_error: true
+```
+
+
+
+
+
+
+
+status
int
+
+
+
+
+Status describes the HTTP status code expected in the response.
+
+
+
+Examples:
+
+
+```yaml
+# Status
+status: 200
+```
+
+
+
+
+
+
+
+
+response_contains
string
+
+
+
+
+ResponseContains describes the text that should be contained in the HTTP response.
+
+
+
+Examples:
+
+
+```yaml
+# ResponseContains
+response_contains: Hello, World
+```
+
+
+
+
+
+
+
+
+log_contains
string
+
+
+
+
+LogContains describes the text that should be contained in the WAF logs.
+
+
+
+Examples:
+
+
+```yaml
+# LogContains
+log_contains: id 920100
+```
+
+
+
+
+
+
+
+
+no_log_contains
string
+
+
+
+
+NoLogContains describes the text that should not be contained in the WAF logs.
+
+
+
+Examples:
+
+
+```yaml
+# NoLogContains
+no_log_contains: id 920100
+```
+
+
+
+
+
+
+
+
+
+Log is used to configure expectations about the log contents.
+
+
+
+Examples:
+
+
+```yaml
+log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+expect_error
bool
+
+
+
+
+When `ExpectError` is true, we don't expect an answer from the WAF, just an error.
+
+
+
+Examples:
+
+
+```yaml
+# ExpectError
+expect_error: false
+```
+
+
+
+
+
+
+
+
+
+
+## Log
+
+Appears in:
+
+
+- Output.log
+
+
+```yaml
+expect_id: 123456
+no_expect_id: 123456
+match_regex: id[:\s"]*123456
+no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+expect_id
int
+
+
+
+
+Expect the given ID to be contained in the log output.
+
+
+
+Examples:
+
+
+```yaml
+expect_id: 123456
+```
+
+
+
+
+
+
+
+
+no_expect_id
int
+
+
+
+
+Expect the given ID _not_ to be contained in the log output.
+
+
+
+Examples:
+
+
+```yaml
+no_expect_id: 123456
+```
+
+
+
+
+
+
+
+
+match_regex
string
+
+
+
+
+Expect the regular expression to match log content for the current test.
+
+
+
+Examples:
+
+
+```yaml
+match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+no_match_regex
string
+
+
+
+
+Expect the regular expression to _not_ match log content for the current test.
+
+
+
+Examples:
+
+
+```yaml
+no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+## FTWOverrides
+FTWOverrides describes platform specific overrides for tests
+
+
+
+
+
+
+
+
+
+
+version
string
+
+
+
+
+The version field designates the version of the schema that validates this file
+
+
+
+Examples:
+
+
+```yaml
+version: v0.1.0
+```
+
+
+
+
+
+
+
+
+
+Meta describes the metadata information
+
+
+
+Examples:
+
+
+```yaml
+meta:
+ engine: libmodsecurity3
+ platform: nginx
+ annotations:
+ os: Debian Bullseye
+ purpose: L7ASR test suite
+```
+
+
+
+
+
+
+
+
+
+List of test override specifications
+
+
+
+Examples:
+
+
+```yaml
+test_overrides:
+ - rule_id: 920100
+ test_ids: [4, 6]
+ reason: |-
+ nginx returns 400 when `Content-Length` header is sent in a
+ `Transfer-Encoding: chunked` request.
+ expect_failure: true
+ output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## FTWOverridesMeta
+
+Appears in:
+
+
+- FTWOverrides.meta
+
+
+```yaml
+engine: libmodsecurity3
+platform: nginx
+annotations:
+ os: Debian Bullseye
+ purpose: L7ASR test suite
+```
+
+
+
+
+
+
+
+engine
string
+
+
+
+
+The name of the WAF engine the tests are expected to run against
+
+
+
+Examples:
+
+
+```yaml
+engine: coraza
+```
+
+
+
+
+
+
+
+
+platform
string
+
+
+
+
+The name of the platform (e.g., web server) the tests are expected to run against
+
+
+
+Examples:
+
+
+```yaml
+platform: nginx
+```
+
+
+
+
+
+
+
+
+annotations
map[string]string
+
+
+
+
+Custom annotations; can be used to add additional meta information
+
+
+
+Examples:
+
+
+```yaml
+annotations:
+ os: Debian Bullseye
+ purpose: L7ASR test suite
+```
+
+
+
+
+
+
+
+
+
+
+## TestOverride
+
+Appears in:
+
+
+- FTWOverrides.test_overrides
+
+
+```yaml
+- rule_id: 920100
+ test_ids: [4, 6]
+ reason: |-
+ nginx returns 400 when `Content-Length` header is sent in a
+ `Transfer-Encoding: chunked` request.
+ expect_failure: true
+ output:
+ status: 200
+ log_contains: nothing
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+rule_id
int
+
+
+
+
+ID of the rule this test targets.
+
+
+
+Examples:
+
+
+```yaml
+rule_id: 920100
+```
+
+
+
+
+
+
+
+
+test_ids
[]int
+
+
+
+
+IDs of the tests for rule_id that overrides should be applied to.
+If this field is not set, the overrides will be applied to all tests of rule_id.
+
+
+
+Examples:
+
+
+```yaml
+test_ids:
+ - 4
+ - 6
+```
+
+
+
+
+
+
+
+
+reason
string
+
+
+
+
+Describes why this override is necessary.
+
+
+
+Examples:
+
+
+```yaml
+reason: |-
+ nginx returns 400 when `Content-Length` header is sent in a
+ `Transfer-Encoding: chunked` request.
+```
+
+
+
+
+
+
+
+
+expect_failure
bool
+
+
+
+
+Whether this test is expected to fail for this particular configuration.
+Default: false
+
+
+
+Examples:
+
+
+```yaml
+expect_failure: true
+```
+
+
+
+
+
+
+
+
+retry_once
bool
+
+
+
+
+Whether a stage should be retried once in case of failure.
+This option is primarily a workaround for a race condition in phase 5,
+where the log entry of a rule may be flushed after the test end marker.
+
+
+
+Examples:
+
+
+```yaml
+retry_once: true
+```
+
+
+
+
+
+
+
+
+
+Specifies overrides on the test output
+
+
+
+Examples:
+
+
+```yaml
+output: 400
+```
+
+
+
+
+
+
+
+
+
+
+## Output
+
+Appears in:
+
+
+- TestOverride.output
+
+
+```yaml
+400
+```
+
+
+
+
+
+
+
+status
int
+
+
+
+
+Status describes the HTTP status code expected in the response.
+
+
+
+Examples:
+
+
+```yaml
+# Status
+status: 200
+```
+
+
+
+
+
+
+
+
+response_contains
string
+
+
+
+
+ResponseContains describes the text that should be contained in the HTTP response.
+
+
+
+Examples:
+
+
+```yaml
+# ResponseContains
+response_contains: Hello, World
+```
+
+
+
+
+
+
+
+
+log_contains
string
+
+
+
+
+LogContains describes the text that should be contained in the WAF logs.
+
+
+
+Examples:
+
+
+```yaml
+# LogContains
+log_contains: id 920100
+```
+
+
+
+
+
+
+
+
+no_log_contains
string
+
+
+
+
+NoLogContains describes the text that should not be contained in the WAF logs.
+
+
+
+Examples:
+
+
+```yaml
+# NoLogContains
+no_log_contains: id 920100
+```
+
+
+
+
+
+
+
+
+
+Log is used to configure expectations about the log contents.
+
+
+
+Examples:
+
+
+```yaml
+log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+expect_error
bool
+
+
+
+
+When `ExpectError` is true, we don't expect an answer from the WAF, just an error.
+
+
+
+Examples:
+
+
+```yaml
+# ExpectError
+expect_error: false
+```
+
+
+
+
+
+
+
+
+
+
+## Log
+
+Appears in:
+
+
+- Output.log
+
+
+```yaml
+expect_id: 123456
+no_expect_id: 123456
+match_regex: id[:\s"]*123456
+no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+expect_id
int
+
+
+
+
+Expect the given ID to be contained in the log output.
+
+
+
+Examples:
+
+
+```yaml
+expect_id: 123456
+```
+
+
+
+
+
+
+
+
+no_expect_id
int
+
+
+
+
+Expect the given ID _not_ to be contained in the log output.
+
+
+
+Examples:
+
+
+```yaml
+no_expect_id: 123456
+```
+
+
+
+
+
+
+
+
+match_regex
string
+
+
+
+
+Expect the regular expression to match log content for the current test.
+
+
+
+Examples:
+
+
+```yaml
+match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+no_match_regex
string
+
+
+
+
+Expect the regular expression to _not_ match log content for the current test.
+
+
+
+Examples:
+
+
+```yaml
+no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+
diff --git a/spec/v1/ftw.md b/spec/v1/ftw.md
index 60159e8..a9630d6 100644
--- a/spec/v1/ftw.md
+++ b/spec/v1/ftw.md
@@ -16,7 +16,7 @@ Metadata parameters are present once per test file and are located by convention
description: "This file contains example tests."
...
```
-What follows are all the possible Metadata parameters that are current supported
+What follows are all the possible Metadata parameters that are currently supported
author
------
diff --git a/types/overrides/examples.go b/types/overrides/examples.go
new file mode 100644
index 0000000..e41cdd5
--- /dev/null
+++ b/types/overrides/examples.go
@@ -0,0 +1,28 @@
+// Copyright 2023 OWASP CRS
+// SPDX-License-Identifier: Apache-2.0
+
+package types
+
+import test "github.com/coreruleset/ftw-tests-schema/types/test"
+
+var (
+ // imported
+ AnnotationsExample = test.AnnotationsExample
+ ReasonExample = test.ReasonExample
+ ExampleLog = test.ExampleLog
+
+ MetaExample = FTWOverridesMeta{
+ Engine: "libmodsecurity3",
+ Platform: "nginx",
+ Annotations: test.AnnotationsExample,
+ }
+ TestOverridesExample = []TestOverride{
+ {
+ RuleId: 920100,
+ TestIds: []int{4, 6},
+ Reason: test.ReasonExample,
+ ExpectFailure: func() *bool { b := true; return &b }(),
+ Output: test.ExampleOutput,
+ },
+ }
+)
diff --git a/types/overrides/types.go b/types/overrides/types.go
new file mode 100644
index 0000000..a3cc363
--- /dev/null
+++ b/types/overrides/types.go
@@ -0,0 +1,93 @@
+// Copyright 2023 OWASP CRS
+// SPDX-License-Identifier: Apache-2.0
+
+//go:generate dstdocgen -package types -path . -structure FTWOverrides -output ./overrides_doc.go
+
+package types
+
+import test "github.com/coreruleset/ftw-tests-schema/types/test"
+
+// FTWOverrides describes platform specific overrides for tests
+type FTWOverrides struct {
+ // description: |
+ // The version field designates the version of the schema that validates this file
+ // examples:
+ // - value: "\"v0.1.0\""
+ Version string `yaml:"version"`
+
+ // description: |
+ // Meta describes the metadata information
+ // examples:
+ // - value: MetaExample
+ Meta FTWOverridesMeta `yaml:"meta"`
+
+ // description: |
+ // List of test override specifications
+ // examples:
+ // - value: TestOverridesExample
+ TestOverrides []TestOverride `yaml:"test_overrides"`
+}
+
+// FTWOverridesMeta describes the metadata information of this yaml file
+type FTWOverridesMeta struct {
+ // description: |
+ // The name of the WAF engine the tests are expected to run against
+ // examples:
+ // - value: "\"coraza\""
+ Engine string `yaml:"engine"`
+
+ // description: |
+ // The name of the platform (e.g., web server) the tests are expected to run against
+ // examples:
+ // - value: "\"nginx\""
+ Platform string `yaml:"platform"`
+
+ // description: |
+ // Custom annotations; can be used to add additional meta information
+ // examples:
+ // - value: AnnotationsExample
+ Annotations map[string]string `yaml:"annotations"`
+}
+
+// TestOverride describes overrides for a single test
+type TestOverride struct {
+ // description: |
+ // ID of the rule this test targets.
+ // examples:
+ // - value: TestOverridesExample[0].RuleId
+ RuleId int `yaml:"rule_id"`
+
+ // description: |
+ // IDs of the tests for rule_id that overrides should be applied to.
+ // If this field is not set, the overrides will be applied to all tests of rule_id.
+ // examples:
+ // - value: TestOverridesExample[0].TestIds
+ TestIds []int `yaml:"test_ids,flow,omitempty"`
+
+ // description: |
+ // Describes why this override is necessary.
+ // examples:
+ // - value: ReasonExample
+ Reason string `yaml:"reason"`
+
+ // description: |
+ // Whether this test is expected to fail for this particular configuration.
+ // Default: false
+ // examples:
+ // - value: true
+ ExpectFailure *bool `yaml:"expect_failure,omitempty"`
+
+ // description: |
+ // Whether a stage should be retried once in case of failure.
+ // This option is primarily a workaround for a race condition in phase 5,
+ // where the log entry of a rule may be flushed after the test end marker.
+ // examples:
+ // - value: true
+ RetryOnce *bool `yaml:"retry_once,omitempty"`
+
+ // description: |
+ // Specifies overrides on the test output
+ // examples:
+ // - value: 400
+ Output test.Output `yaml:"output"`
+}
diff --git a/types/overrides/types_test.go b/types/overrides/types_test.go
new file mode 100644
index 0000000..929ea16
--- /dev/null
+++ b/types/overrides/types_test.go
@@ -0,0 +1,103 @@
+// Copyright 2023 OWASP CRS
+// SPDX-License-Identifier: Apache-2.0
+
+package types
+
+import (
+ "testing"
+
+ "github.com/goccy/go-yaml"
+ "github.com/stretchr/testify/assert"
+)
+
+var overridesYaml = `---
+version: "v0.0.0"
+meta:
+ engine: "libmodsecurity3"
+ platform: "nginx"
+ annotations:
+ os: "Debian Bullseye"
+ purpose: "L7ASR test suite"
+test_overrides:
+ - rule_id: 920100
+ test_ids: [4, 6]
+ reason: |-
+ nginx returns 400 when ` + "`" + "Content-Length" + "`" + ` header is sent in a
+ ` + "`" + `Transfer-Encoding: chunked` + "`" + ` request.
+ expect_failure: true
+ output:
+ status: 200
+ log_contains: "nothing"
+ log:
+ expect_id: 123456
+ no_expect_id: 123456
+ match_regex: 'id[:\s"]*123456'
+ no_match_regex: 'id[:\s"]*123456'
+ expect_error: true
+`
+
+var ftwOverrides = &FTWOverrides{
+ Version: "v0.0.0",
+ Meta: MetaExample,
+ TestOverrides: TestOverridesExample,
+}
+
+func TestUnmarshalFTWOverrides(t *testing.T) {
+ var overrides FTWOverrides
+
+ err := yaml.Unmarshal([]byte(overridesYaml), &overrides)
+
+ if err != nil {
+ t.Errorf("Unmarshal: %v", err)
+ }
+
+ assert.Equal(t, ftwOverrides.Version, overrides.Version)
+}
+
+func TestUnmarshalMeta(t *testing.T) {
+ var overrides FTWOverrides
+ assert := assert.New(t)
+
+ err := yaml.Unmarshal([]byte(overridesYaml), &overrides)
+ if err != nil {
+ t.Errorf("Unmarshal: %v", err)
+ }
+
+ meta := overrides.Meta
+ expectedMeta := ftwOverrides.Meta
+ assert.Equal(expectedMeta.Engine, meta.Engine)
+ assert.Equal(expectedMeta.Platform, meta.Platform)
+ assert.NotNil(meta.Annotations)
+ assert.Len(meta.Annotations, len(expectedMeta.Annotations))
+
+ for key, value := range meta.Annotations {
+ expectedValue := expectedMeta.Annotations[key]
+ assert.Equal(expectedValue, value)
+ }
+}
+
+func TestUnmarmarshalTestOverrides(t *testing.T) {
+ var overrides FTWOverrides
+ asserter := assert.New(t)
+
+ err := yaml.Unmarshal([]byte(overridesYaml), &overrides)
+ if err != nil {
+ t.Errorf("Unmarshal: %v", err)
+ }
+
+ testOverrides := overrides.TestOverrides
+ expectedTestOverrides := ftwOverrides.TestOverrides
+ asserter.NotNil(testOverrides)
+ asserter.Len(testOverrides, len(expectedTestOverrides))
+
+ for index, testOverride := range testOverrides {
+ expectedTestOverride := expectedTestOverrides[index]
+ asserter.Equal(expectedTestOverride.RuleId, testOverride.RuleId)
+ asserter.ElementsMatch(expectedTestOverride.TestIds, testOverride.TestIds)
+ asserter.Equal(expectedTestOverride.Reason, testOverride.Reason)
+ asserter.Equal(expectedTestOverride.ExpectFailure, testOverride.ExpectFailure)
+ if !assert.ObjectsAreEqual(expectedTestOverride.Output, testOverride.Output) {
+ asserter.Failf("Output:", "%v != %v", testOverride.Output, expectedTestOverride.Output)
+ }
+ }
+}
diff --git a/types/test/examples.go b/types/test/examples.go
new file mode 100644
index 0000000..a53818c
--- /dev/null
+++ b/types/test/examples.go
@@ -0,0 +1,62 @@
+// Copyright 2023 OWASP CRS
+// SPDX-License-Identifier: Apache-2.0
+
+package types
+
+var (
+ ExampleTests = []Test{
+ ExampleTest,
+ }
+ ExampleTest = Test{
+ TestTitle: "123456-1",
+ TestDescription: "Unix RCE using `time`",
+ Stages: ExampleStages,
+ }
+ ExampleStage = Stage{
+ Description: "Get cookie from server",
+ Input: ExampleInput,
+ Output: ExampleOutput,
+ }
+ ExampleStages = []Stage{
+ ExampleStage,
+ }
+ ExampleHeaders = map[string]string{
+ "User-Agent": "CRS Tests",
+ "Host": "localhost",
+ "Accept": "*/*",
+ }
+ ExampleInput = Input{
+ DestAddr: strPtr("192.168.0.1"),
+ Port: intPtr(8080),
+ Protocol: strPtr("http"),
+ URI: strPtr("/test"),
+ Version: strPtr("HTTP/1.1"),
+ Headers: ExampleHeaders,
+ Method: strPtr("REPORT"),
+ Data: nil,
+ EncodedRequest: "TXkgRGF0YQo=",
+ SaveCookie: boolPtr(false),
+ StopMagic: boolPtr(true),
+ AutocompleteHeaders: boolPtr(false),
+ }
+ ExampleOutput = Output{
+ Status: 200,
+ ResponseContains: "",
+ LogContains: "nothing",
+ NoLogContains: "",
+ Log: ExampleLog,
+ ExpectError: boolPtr(true),
+ }
+ ExampleLog = Log{
+ ExpectId: 123456,
+ NoExpectId: 123456,
+ MatchRegex: `id[:\s"]*123456`,
+ NoMatchRegex: `id[:\s"]*123456`,
+ }
+ AnnotationsExample = map[string]string{
+ "os": "Debian Bullseye",
+ "purpose": "L7ASR test suite",
+ }
+ ReasonExample = "nginx returns 400 when `Content-Length` header is sent in a\n" +
+ "`Transfer-Encoding: chunked` request."
+)
diff --git a/types/test/helpers.go b/types/test/helpers.go
new file mode 100644
index 0000000..4c38d7b
--- /dev/null
+++ b/types/test/helpers.go
@@ -0,0 +1,16 @@
+// Copyright 2023 OWASP CRS
+// SPDX-License-Identifier: Apache-2.0
+
+package types
+
+func intPtr(i int) *int {
+ return &i
+}
+
+func boolPtr(b bool) *bool {
+ return &b
+}
+
+func strPtr(s string) *string {
+ return &s
+}
diff --git a/types/types.go b/types/test/types.go
similarity index 71%
rename from types/types.go
rename to types/test/types.go
index 51f3e69..1db55df 100644
--- a/types/types.go
+++ b/types/test/types.go
@@ -1,59 +1,9 @@
-package types
-
-// Copyright 2023 Felipe Zipitria
+// Copyright 2023 OWASP CRS
// SPDX-License-Identifier: Apache-2.0
-//go:generate dstdocgen -package types -path . -structure FTWTest -output ./types_doc.go
-
-func intPtr(i int) *int {
- return &i
-}
-
-func boolPtr(b bool) *bool {
- return &b
-}
-
-func strPtr(s string) *string {
- return &s
-}
+//go:generate dstdocgen -package types -path . -structure FTWTest -output ./test_doc.go
-var (
- exampleStageData = StageData{
- Input: exampleInput,
- Output: exampleOutput,
- }
- exampleStages = []Stage{
- {
- exampleStageData,
- },
- }
- exampleHeaders = map[string]string{
- "User-Agent": "CRS Tests",
- "Host": "localhost",
- "Accept": "*/*",
- }
- exampleInput = Input{
- DestAddr: strPtr("192.168.0.1"),
- Port: intPtr(8080),
- Protocol: strPtr("http"),
- URI: strPtr("/test"),
- Version: strPtr("HTTP/1.1"),
- Headers: exampleHeaders,
- Method: strPtr("REPORT"),
- Data: nil,
- EncodedRequest: "TXkgRGF0YQo=",
- SaveCookie: boolPtr(false),
- StopMagic: boolPtr(true),
- AutocompleteHeaders: boolPtr(false),
- }
- exampleOutput = Output{
- Status: []int{200},
- ResponseContains: "",
- LogContains: "nothing",
- NoLogContains: "",
- ExpectError: boolPtr(true),
- }
-)
+package types
// Welcome to the FTW YAMLFormat documentation.
// In this document we will explain all the possible options that can be used within the YAML format.
@@ -64,7 +14,7 @@ var (
type FTWTest struct {
// description: |
// Meta describes the metadata information of this yaml test file
- Meta Meta `yaml:"meta"`
+ Meta FTWTestMeta `yaml:"meta"`
// description: |
// FileName is the name of the file where these tests are.
@@ -76,13 +26,12 @@ type FTWTest struct {
// description: |
// Tests is a list of FTW tests
// examples:
- // - name: Tests
- // value: "\"the tests\""
+ // - value: ExampleTests
Tests []Test `yaml:"tests"`
}
-// Meta describes the metadata information of this yaml test file
-type Meta struct {
+// FTWTestMeta describes the metadata information of this yaml test file
+type FTWTestMeta struct {
// description: |
// Author is the list of authors that added content to this file
// examples:
@@ -95,6 +44,8 @@ type Meta struct {
// examples:
// - name: Enabled
// value: false
+ //
+ // Deprecated: ignored; use platform specific overrides instead
Enabled *bool `yaml:"enabled,omitempty"`
// description: |
@@ -122,52 +73,85 @@ type Meta struct {
// Test is an individual test. One test can have multiple stages.
type Test struct {
// description: |
- // TestTitle the title of this particular test. It is used for inclusion/exclusion of each run by the tool.
+ // TestTitle is the title of this particular test. It is used for inclusion/exclusion of each run by the tool.
+ // examples:
+ // - value: ExampleTest.TestTitle
+ //
+ // Deprecated: use `rule_id` and `test_id`
+ TestTitle string `yaml:"test_title,omitempty"`
+
+ // description: |
+ // RuleId is the ID of the rule this test targets
// examples:
- // - name: TestTitle
- // value: "\"920100-1\""
- TestTitle string `yaml:"test_title"`
+ // - name: RuleId
+ // value: 123456
+ RuleId int `yaml:"rule_id"`
+
+ // description: |
+ // TestId is the ID of the test, in relation to `rule_id`
+ // examples:
+ // - name: TestId
+ // value: 4
+ TestId int `yaml:"test_id"`
// description: |
// TestDescription is the description for this particular test. Should be used to describe the internals of
// the specific things this test is targeting.
// examples:
- // - name: TestDescription
- // value: "\"This test targets something\""
+ // - value: ExampleTest.TestDescription
TestDescription string `yaml:"desc,omitempty"`
// description: |
// Stages is the list of all the stages to perform this test.
// examples:
- // - name: Stages
- // value: exampleStages
+ // - value: ExampleStages
Stages []Stage `yaml:"stages"`
}
// Stage is a list of stages
type Stage struct {
// description: |
- // StageData is an individual test stage
+ // StageData is an individual test stage.
+ //
+ // Deprecated: use the other fields of `Stage`
+ SD StageData `yaml:"stage,omitempty"`
+ // description: |
+ // Describes the purpose of this stage.
// examples:
- // - name: StageData
- // value: exampleStageData
- SD StageData `yaml:"stage"`
+ // - value: ExampleStage.Description
+ Description string `yaml:"description,omitempty"`
+
+ // description: |
+ // Input is the data that is passed to the test
+ // examples:
+ // - name: Input
+ // value: ExampleInput
+ Input Input `yaml:"input"`
+
+ // description: |
+ // Output is the data that is returned from the test
+ // examples:
+ // - name: Output
+ // value: ExampleOutput
+ Output Output `yaml:"output"`
}
// StageData is the data that is passed to the test, and the data that is returned from the test
+//
+// Deprecated: use the other fields of `stage`
type StageData struct {
// description: |
// Input is the data that is passed to the test
// examples:
// - name: Input
- // value: exampleInput
+ // value: ExampleInput
Input Input `yaml:"input"`
// description: |
// Output is the data that is returned from the test
// examples:
// - name: Output
- // value: exampleOutput
+ // value: ExampleOutput
Output Output `yaml:"output"`
}
@@ -220,7 +204,7 @@ type Input struct {
// Method allows you to declare headers that the test should send.
// examples:
// - name: Headers
- // value: exampleHeaders
+ // value: ExampleHeaders
Headers map[string]string `yaml:"headers,omitempty" koanf:"headers,omitempty"`
// description: |
@@ -242,6 +226,8 @@ type Input struct {
// examples:
// - name: StopMagic
// value: false
+ //
+ // Deprecated: use AutocompleteHeaders instead
StopMagic *bool `yaml:"stop_magic" koanf:"stop_magic,omitempty"`
// description: |
@@ -265,17 +251,19 @@ type Input struct {
// examples:
// - name: RAWRequest
// value: "\"TXkgRGF0YQo=\""
+ //
+ // Deprecated: use `encoded_request`
RAWRequest string `yaml:"raw_request,omitempty" koanf:"raw_request,omitempty"`
}
// Output is the response expected from the test
type Output struct {
// description: |
- // Status describes the HTTP status error code expected as response.
+ // Status describes the HTTP status code expected in the response.
// examples:
// - name: Status
- // value: [200]
- Status []int `yaml:"status,flow,omitempty"`
+ // value: 200
+ Status int `yaml:"status,omitempty"`
// description: |
// ResponseContains describes the text that should be contained in the HTTP response.
@@ -289,6 +277,8 @@ type Output struct {
// examples:
// - name: LogContains
// value: "\"id 920100\""
+ //
+ // Deprecated: use Log instead
LogContains string `yaml:"log_contains,omitempty"`
// description: |
@@ -296,8 +286,16 @@ type Output struct {
// examples:
// - name: NoLogContains
// value: "\"id 920100\""
+ //
+ // Deprecated: use Log instead
NoLogContains string `yaml:"no_log_contains,omitempty"`
+ // description: |
+ // Log is used to configure expectations about the log contents.
+ // examples:
+ // - value: ExampleLog
+ Log Log `yaml:"log,omitempty"`
+
// description: |
// When `ExpectError` is true, we don't expect an answer from the WAF, just an error.
// examples:
@@ -305,3 +303,30 @@ type Output struct {
// value: false
ExpectError *bool `yaml:"expect_error,omitempty"`
}
+
+// Log is used to configure expectations about the log contents.
+type Log struct {
+ // description: |
+ // Expect the given ID to be contained in the log output.
+ // examples:
+ // - value: ExampleLog.ExpectId
+ ExpectId int `yaml:"expect_id,omitempty"`
+
+ // description: |
+ // Expect the given ID _not_ to be contained in the log output.
+ // examples:
+ // - value: ExampleLog.NoExpectId
+ NoExpectId int `yaml:"no_expect_id,omitempty"`
+
+ // description: |
+ // Expect the regular expression to match log content for the current test.
+ // examples:
+ // - value: ExampleLog.MatchRegex
+ MatchRegex string `yaml:"match_regex,omitempty"`
+
+ // description: |
+ // Expect the regular expression to _not_ match log content for the current test.
+ // examples:
+ // - value: ExampleLog.NoMatchRegex
+ NoMatchRegex string `yaml:"no_match_regex,omitempty"`
+}
diff --git a/types/test/types_test.go b/types/test/types_test.go
new file mode 100644
index 0000000..47a8a8b
--- /dev/null
+++ b/types/test/types_test.go
@@ -0,0 +1,139 @@
+// Copyright 2023 OWASP CRS
+// SPDX-License-Identifier: Apache-2.0
+
+package types
+
+import (
+ "testing"
+
+ "github.com/goccy/go-yaml"
+ "github.com/stretchr/testify/assert"
+)
+
+var testYaml = `---
+filename: "testYaml.yaml"
+meta:
+ author: "ftw-tests-schema"
+ enabled: true
+ name: "testYaml"
+ description: "Simple YAML to test that the schema is working."
+tests:
+ - test_title: 1234-1
+ rule_id: 1234
+ test_id: 1
+ desc: "Test that the schema is working."
+ stages:
+ - input:
+ dest_addr: "192.168.0.1"
+ port: 8080
+ method: "REPORT"
+ headers:
+ User-Agent: "CRS Tests"
+ Host: "localhost"
+ Accept: "*/*"
+ encoded_request: "TXkgRGF0YQo="
+ uri: "/test"
+ protocol: "http"
+ autocomplete_headers: false
+ stop_magic: true
+ save_cookie: false
+ output:
+ status: 200
+ response_contains: ""
+ log_contains: "nothing"
+ no_log_contains: ""
+ - test_title: 1234-2
+ stages:
+ - input:
+ dest_addr: "127.0.0.1"
+ port: 80
+ method: "OPTIONS"
+ headers:
+ User-Agent: "FTW Schema Tests"
+ Host: "localhost"
+ output:
+ status: 200
+`
+
+var ftwTest = &FTWTest{
+ FileName: "testYaml.yaml",
+ Meta: FTWTestMeta{
+ Author: "ftw-tests-schema",
+ Enabled: boolPtr(true),
+ Name: "testYaml",
+ Description: "Simple YAML to test that the schema is working.",
+ },
+ Tests: []Test{
+ {
+ TestTitle: "1234-1",
+ RuleId: 1234,
+ TestId: 1,
+ TestDescription: "Test that the schema is working.",
+ Stages: []Stage{
+ {
+ Description: ExampleStage.Description,
+ Input: ExampleInput,
+ Output: ExampleOutput,
+ },
+ },
+ },
+ {
+ TestTitle: "1234-2",
+ Stages: []Stage{
+ {
+ Input: Input{
+ DestAddr: strPtr("127.0.0.1"),
+ Port: intPtr(80),
+ Method: strPtr("OPTIONS"),
+ Headers: map[string]string{
+ "User-Agent": "FTW Schema Tests",
+ "Host": "localhost",
+ },
+ },
+ Output: Output{
+ Status: 200,
+ },
+ },
+ },
+ },
+ },
+}
+
+func TestUnmarshalFTWTest(t *testing.T) {
+ var ftw FTWTest
+ assert := assert.New(t)
+
+ err := yaml.Unmarshal([]byte(testYaml), &ftw)
+ assert.NoError(err)
+
+ assert.Equal(ftwTest.FileName, ftw.FileName)
+ assert.Equal(ftwTest.Meta.Author, ftw.Meta.Author)
+ assert.Equal(ftwTest.Meta.Enabled, ftw.Meta.Enabled)
+ assert.Equal(ftwTest.Meta.Name, ftw.Meta.Name)
+ assert.Equal(ftwTest.Meta.Description, ftw.Meta.Description)
+ assert.Len(ftwTest.Tests, len(ftw.Tests))
+
+ for i, test := range ftw.Tests {
+ expectedTest := ftwTest.Tests[i]
+ assert.Equal(expectedTest.TestTitle, test.TestTitle)
+ assert.Equal(expectedTest.RuleId, test.RuleId)
+ assert.Equal(expectedTest.TestId, test.TestId)
+ assert.Len(test.Stages, len(expectedTest.Stages))
+
+ for j, stage := range test.Stages {
+ expectedStage := expectedTest.Stages[j]
+ assert.Equal(expectedStage.Input.DestAddr, stage.Input.DestAddr)
+ assert.Equal(expectedStage.Input.Port, stage.Input.Port)
+ assert.Equal(expectedStage.Input.Method, stage.Input.Method)
+ assert.Len(stage.Input.Headers, len(expectedStage.Input.Headers))
+
+ for k, header := range stage.Input.Headers {
+ expectedHeader := expectedStage.Input.Headers[k]
+ assert.Equal(expectedHeader, header)
+ }
+
+ assert.Equal(expectedStage.Output.NoLogContains, stage.Output.NoLogContains)
+ assert.Equal(expectedStage.Output.Status, stage.Output.Status)
+ }
+ }
+}
diff --git a/types/types_test.go b/types/types_test.go
deleted file mode 100644
index cf09e27..0000000
--- a/types/types_test.go
+++ /dev/null
@@ -1,193 +0,0 @@
-package types
-
-import (
- "testing"
-
- "github.com/goccy/go-yaml"
-)
-
-var testInput = `---
-dest_addr: "127.0.0.1"
-port: 80
-headers:
- User-Agent: "FTW Schema Tests"
- Host: "localhost"
-`
-
-var testYaml = `---
-filename: "testYaml.yaml"
-meta:
- author: "ftw-tests-schema"
- enabled: true
- name: "testYaml"
- description: "Simple YAML to test that the schema is working."
-tests:
- - test_title: 1234-1
- desc: "Test that the schema is working."
- stages:
- - stage:
- input:
- dest_addr: "127.0.0.1"
- port: 80
- headers:
- User-Agent: "FTW Schema Tests"
- Host: "localhost"
- output:
- no_log_contains: 'id "1234"'
- - test_title: 1234-2
- stages:
- - stage:
- input:
- dest_addr: "127.0.0.1"
- port: 80
- method: "OPTIONS"
- headers:
- User-Agent: "FTW Schema Tests"
- Host: "localhost"
- output:
- status: [200, 204]
-`
-
-var inputTest = &Input{
- DestAddr: strPtr("127.0.0.1"),
- Port: intPtr(80),
- Headers: map[string]string{
- "User-Agent": "FTW Schema Tests",
- "Host": "localhost",
- },
-}
-
-var ftwTest = &FTWTest{
- FileName: "testYaml.yaml",
- Meta: Meta{
- Author: "ftw-tests-schema",
- Enabled: boolPtr(true),
- Name: "testYaml",
- Description: "Simple YAML to test that the schema is working.",
- },
- Tests: []Test{
- {
- TestTitle: "1234-1",
- TestDescription: "Test that the schema is working.",
- Stages: []Stage{
- {
- SD: StageData{
- Input: Input{
- DestAddr: strPtr("127.0.0.1"),
- Port: intPtr(80),
- Headers: map[string]string{
- "User-Agent": "FTW Schema Tests",
- "Host": "localhost",
- },
- },
- Output: Output{
- NoLogContains: "id \"1234\"",
- },
- },
- },
- },
- },
- {
- TestTitle: "1234-2",
- Stages: []Stage{
- {
- SD: StageData{
- Input: Input{
- DestAddr: strPtr("127.0.0.1"),
- Port: intPtr(80),
- Method: strPtr("OPTIONS"),
- Headers: map[string]string{
- "User-Agent": "FTW Schema Tests",
- "Host": "localhost",
- },
- },
- Output: Output{
- Status: []int{200, 204},
- },
- },
- },
- },
- },
- },
-}
-
-func TestUnmarshalFTWTest(t *testing.T) {
- var ftw FTWTest
-
- err := yaml.Unmarshal([]byte(testYaml), &ftw)
-
- if err != nil {
- t.Errorf("Unmarshal: %v", err)
- }
-
- if ftw.FileName != ftwTest.FileName {
- t.Errorf("FileName: %v != %v", ftw.FileName, ftwTest.FileName)
- }
- if ftw.Meta.Author != ftwTest.Meta.Author {
- t.Errorf("Author: %v != %v", ftw.Meta.Author, ftwTest.Meta.Author)
- }
- if *ftw.Meta.Enabled != *ftwTest.Meta.Enabled {
- t.Errorf("Enabled: %v != %v", ftw.Meta.Enabled, ftwTest.Meta.Enabled)
- }
- if ftw.Meta.Name != ftwTest.Meta.Name {
- t.Errorf("Name: %v != %v", ftw.Meta.Name, ftwTest.Meta.Name)
- }
- if ftw.Meta.Description != ftwTest.Meta.Description {
- t.Errorf("Description: %v != %v", ftw.Meta.Description, ftwTest.Meta.Description)
- }
- if len(ftw.Tests) != len(ftwTest.Tests) {
- t.Errorf("Tests: %v != %v", len(ftw.Tests), len(ftwTest.Tests))
- }
- for i, test := range ftw.Tests {
- if test.TestTitle != ftwTest.Tests[i].TestTitle {
- t.Errorf("TestTitle: %v != %v", test.TestTitle, ftwTest.Tests[i].TestTitle)
- }
- if len(test.Stages) != len(ftwTest.Tests[i].Stages) {
- t.Errorf("Stages: %v != %v", len(test.Stages), len(ftwTest.Tests[i].Stages))
- }
- for j, stage := range test.Stages {
- if *stage.SD.Input.DestAddr != *ftwTest.Tests[i].Stages[j].SD.Input.DestAddr {
- t.Errorf("DestAddr: %v != %v", *stage.SD.Input.DestAddr, *ftwTest.Tests[i].Stages[j].SD.Input.DestAddr)
- }
- if *stage.SD.Input.Port != *ftwTest.Tests[i].Stages[j].SD.Input.Port {
- t.Errorf("Port: %v != %v", *stage.SD.Input.Port, *ftwTest.Tests[i].Stages[j].SD.Input.Port)
- }
- if stage.SD.Input.Method != nil && *stage.SD.Input.Method != *ftwTest.Tests[i].Stages[j].SD.Input.Method {
- t.Errorf("Method: %v != %v", stage.SD.Input.Method, ftwTest.Tests[i].Stages[j].SD.Input.Method)
- }
- if len(stage.SD.Input.Headers) != len(ftwTest.Tests[i].Stages[j].SD.Input.Headers) {
- t.Errorf("Headers: %v != %v", len(stage.SD.Input.Headers), len(ftwTest.Tests[i].Stages[j].SD.Input.Headers))
- }
- for k, header := range stage.SD.Input.Headers {
- if header != ftwTest.Tests[i].Stages[j].SD.Input.Headers[k] {
- t.Errorf("Header: %v != %v", header, ftwTest.Tests[i].Stages[j].SD.Input.Headers[k])
- }
- }
- if stage.SD.Output.NoLogContains != ftwTest.Tests[i].Stages[j].SD.Output.NoLogContains {
- t.Errorf("NoLogContains: %v != %v", stage.SD.Output.NoLogContains, ftwTest.Tests[i].Stages[j].SD.Output.NoLogContains)
- }
- if len(stage.SD.Output.Status) != len(ftwTest.Tests[i].Stages[j].SD.Output.Status) {
- t.Errorf("Status: %v != %v", len(stage.SD.Output.Status), len(ftwTest.Tests[i].Stages[j].SD.Output.Status))
- }
- }
- }
-}
-
-func TestUnmarshalInput(t *testing.T) {
- var input Input
-
- err := yaml.Unmarshal([]byte(testInput), &input)
- if err != nil {
- t.Errorf("Unmarshal: %v", err)
- }
-
- if input.DestAddr != nil && *input.DestAddr != *inputTest.DestAddr {
- t.Errorf("DestAddr: %v != %v", *input.DestAddr, *inputTest.DestAddr)
- }
- if input.Port != nil && *input.Port != *inputTest.Port {
- t.Errorf("Port: %v != %v", *input.Port, *inputTest.Port)
- }
- if input.Method != nil && *input.Method != *inputTest.Method {
- t.Errorf("Method: %v != %v", *input.Method, *inputTest.Method)
- }
-}