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 FTWTestMeta + +
+
+ +Meta describes the metadata information of this yaml test file + +
+ +
+ +
+ +tests []Test + +
+
+ +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 []Stage + +
+
+ +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 +``` + + + +
+ +
+ +stage StageData + +
+
+ +StageData is an individual test stage. + +
+ +
+ +
+ +description string + +
+
+ +Describes the purpose of this stage. + + + +Examples: + + +```yaml +description: Get cookie from server +``` + + +
+ +
+ +
+ +input Input + +
+
+ +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 Output + +
+
+ +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 Input + +
+
+ +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 Output + +
+
+ +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 Log + +
+
+ +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 FTWOverridesMeta + +
+
+ +Meta describes the metadata information + + + +Examples: + + +```yaml +meta: + engine: libmodsecurity3 + platform: nginx + annotations: + os: Debian Bullseye + purpose: L7ASR test suite +``` + + +
+ +
+ +
+ +test_overrides []TestOverride + +
+
+ +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 +``` + + +
+ +
+ +
+ +output Output + +
+
+ +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 Log + +
+
+ +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) - } -}