From 3bc288225c86a681baf6bcba8024ce61b433fee7 Mon Sep 17 00:00:00 2001
From: Max Leske <250711+theseion@users.noreply.github.com>
Date: Sun, 14 Jul 2024 20:55:42 +0200
Subject: [PATCH] feat: add support for Albedo response reflection
---
spec/v2.1.0/ftw.md | 2220 +++++++++++++++++
.../waf-platform-overrides-schema-v2.1.0.json | 1 +
spec/v2.1.0/waf-tests-schema-v2.1.0.json | 1 +
types/examples.go | 14 +-
types/types.go | 52 +-
types/types_test.go | 38 +-
6 files changed, 2315 insertions(+), 11 deletions(-)
create mode 100644 spec/v2.1.0/ftw.md
create mode 100755 spec/v2.1.0/waf-platform-overrides-schema-v2.1.0.json
create mode 100755 spec/v2.1.0/waf-tests-schema-v2.1.0.json
diff --git a/spec/v2.1.0/ftw.md b/spec/v2.1.0/ftw.md
new file mode 100644
index 0000000..687abe1
--- /dev/null
+++ b/spec/v2.1.0/ftw.md
@@ -0,0 +1,2220 @@
+## 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
+
+
+
+
+
+
+
+rule_id
uint
+
+
+
+
+RuleId is the ID of the rule this test targets.
+
+
+
+Examples:
+
+
+```yaml
+# RuleId
+rule_id: 123456
+```
+
+
+
+
+
+
+
+
+
+Tests is a list of FTW tests
+
+
+
+Examples:
+
+
+```yaml
+tests:
+ - test_title: 123456-1
+ ruleid: 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
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 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
+```
+
+
+
+
+
+
+
+
+tags
[]string
+
+
+
+
+description: |
+ Tags is list of strings that can be used for arbitrary grouping of tests.
+ examples:
+ - name: Tags
+ value: ["PHP", "bug-123"]
+
+
+
+
+
+
+
+
+
+## Test
+
+Appears in:
+
+
+- FTWTest.tests
+
+
+```yaml
+- test_title: 123456-1
+ ruleid: 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
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 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 types. It is used for inclusion/exclusion of each run by the tool.
+
+
+
+Examples:
+
+
+```yaml
+test_title: 123456-1
+```
+
+
+
+
+
+
+
+
+test_id
uint
+
+
+
+
+TestId is the ID of the test, in relation to `rule_id`.
+When this field is not set, the ID will be inferred from the
+position.
+
+
+
+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
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+tags
[]string
+
+
+
+
+description: |
+ Tags is list of strings that can be used for arbitrary grouping of tests.
+ examples:
+ - name: Tags
+ value: ["PHP", "bug-123"]
+
+
+
+
+
+
+
+
+
+## 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
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+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
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## Input
+
+Appears in:
+
+
+- Stage.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=
+```
+
+
+
+
+
+
+
+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
+```
+
+
+
+
+
+
+
+
+follow_redirect
bool
+
+
+
+
+FollowRedirect will expect the previous stage of the same test to have received a
+redirect response, it will fail the test otherwise. The redirect location will be used
+to send the request for the current stage and any settings for port, protocol, address,
+or URI will be ignored.
+
+
+
+Examples:
+
+
+```yaml
+# follow_redirect
+follow_redirect: true
+```
+
+
+
+
+
+
+
+
+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
+
+
+
+
+Headers 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
+```
+
+
+
+
+
+
+
+
+encoded_data
string
+
+
+
+
+EncodedData allows you to declare the payload as a base64 encoded string, which
+will be decoded into bytes and sent verbatimt to the server. This allows for complex
+payloads that include invisible characters or invalid Unicode byte sequences.
+
+
+
+Examples:
+
+
+```yaml
+# encoded_data
+encoded_data: c29tZXRoaW5nIHdpdGgKbmV3bGluZQo=
+```
+
+
+
+
+
+
+
+
+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=
+```
+
+
+
+
+
+
+
+
+
+description: |
+ Response describes a response from the web server that a WAF is expected to analyse.
+ Note: This functionality requires a backend that can send the specified request to the
+ reverse proxy. Currently, only Albedo (https://github.com/coreruleset/albedo) is supported.
+ example:
+ - name Response
+ value: ExampleResponse
+
+
+
+
+
+
+
+
+
+## Response
+
+Appears in:
+
+
+- Input.response
+
+
+
+
+
+
+
+
+
+headers
map[string]string
+
+
+
+
+Headers defines the headers the response will carry.
+
+
+
+Examples:
+
+
+```yaml
+# Headers
+headers:
+ Accept: '*/*'
+ Host: localhost
+ User-Agent: CRS Tests
+```
+
+
+
+
+
+
+
+
+status
int
+
+
+
+
+Status describes the HTTP status code of the response. Defaults to `200` if omitted.
+
+
+
+Examples:
+
+
+```yaml
+# Status
+status: 302
+```
+
+
+
+
+
+
+
+
+body
string
+
+
+
+
+Body defines the body of the response as a plain string.
+
+
+
+Examples:
+
+
+```yaml
+# Body
+body: |
+ {"aJsonDocument": ["in the response"]}
+```
+
+
+
+
+
+
+
+
+encoded_body
string
+
+
+
+
+EncodedBody defines the body of the response as a base64 encoded string. This is useful if the response
+needs to contain non-printable characters.
+
+
+
+Examples:
+
+
+```yaml
+# EncodedBody
+encoded_body: eyJhSnNvbkRvY3VtZW50IjogWyJpbiB0aGUgcmVzcG9uc2UiXX0=
+```
+
+
+
+
+
+
+
+
+log_message
string
+
+
+
+
+LogMessage specifies a message to be printed in the log of the backend server that sends the response.
+This can be helpful when debugging, to match resopnses sent by the backend to test executions.
+
+
+
+Examples:
+
+
+```yaml
+# LogMessage
+log_message: Response splitting test 1
+```
+
+
+
+
+
+
+
+
+
+
+## Output
+
+Appears in:
+
+
+- Stage.output
+
+
+```yaml
+# Output
+status: 200
+response_contains: HTTP/1.1
+log_contains: nothing
+no_log_contains: everything
+log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 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_ids:
+ - 123456
+ no_expect_ids:
+ - 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
+```
+
+
+
+
+
+
+
+
+retry_once
bool
+
+
+
+
+When `RetryOnce` is true, the test run will be retried once upon failures. This options
+primary purpose is to work around a race condition in phase 5, where the log entry for
+a phase 5 rule may appear after the end marker of the previous test.
+
+
+
+
+
+
+
+isolated
bool
+
+
+
+
+Isolated specifies that the test is expected to trigger a single rule only.
+If the rule triggers any other rule than the (single) one specified in
+expect_ids, the test fill be considered a failure.
+Default: false
+
+
+
+Examples:
+
+
+```yaml
+# Isolated
+isolated: true
+```
+
+
+
+
+
+
+
+
+
+
+## Log
+
+Appears in:
+
+
+- Output.log
+
+
+```yaml
+expect_ids:
+ - 123456
+no_expect_ids:
+ - 123456
+match_regex: id[:\s"]*123456
+no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+expect_ids
[]uint
+
+
+
+
+description: |
+ Expect the given IDs to be contained in the log output.
+ examples:
+ -value: ExampleLog.ExpectIds
+
+
+
+
+
+
+
+no_expect_ids
[]uint
+
+
+
+
+Expect the given IDs _not_ to be contained in the log output.
+
+
+
+Examples:
+
+
+```yaml
+no_expect_ids:
+ - 123456
+```
+
+
+
+
+
+
+
+
+match_regex
string
+
+
+
+
+Expect the regular expression to match log content for the current types.
+
+
+
+Examples:
+
+
+```yaml
+match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+no_match_regex
string
+
+
+
+
+Expect the regular expression to _not_ match log content for the current types.
+
+
+
+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.
+ output:
+ status: 200
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 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.
+ output:
+ status: 200
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+rule_id
uint
+
+
+
+
+ID of the rule this test targets.
+
+
+
+Examples:
+
+
+```yaml
+rule_id: 920100
+```
+
+
+
+
+
+
+
+
+test_ids
[]uint
+
+
+
+
+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
+```
+
+
+
+
+
+
+
+
+stage_ids
[]uint
+
+
+
+
+IDs of the stages to which overrides should be applied.
+Stage IDs listed will be overridden for all test IDs listed in `TestIds`.
+If this field is not set, the overrides will be applied to all stages.
+
+
+
+
+
+
+
+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.
+```
+
+
+
+
+
+
+
+
+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.
+This definition *replaces* the output definition of the test.
+
+
+
+Examples:
+
+
+```yaml
+output:
+ status: 200
+ response_contains: HTTP/1.1
+ log_contains: nothing
+ no_log_contains: everything
+ log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+ expect_error: true
+```
+
+
+
+
+
+
+
+
+
+
+## types.Output
+Output defines the expectations of a test
+
+Appears in:
+
+
+- TestOverride.output
+
+
+```yaml
+status: 200
+response_contains: HTTP/1.1
+log_contains: nothing
+no_log_contains: everything
+log:
+ expect_ids:
+ - 123456
+ no_expect_ids:
+ - 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_ids:
+ - 123456
+ no_expect_ids:
+ - 123456
+ match_regex: id[:\s"]*123456
+ no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+expect_error
types.bool
+
+
+
+
+When `ExpectError` is true, we don't expect an answer from the WAF, just an error.
+
+
+
+Examples:
+
+
+```yaml
+# ExpectError
+expect_error: false
+```
+
+
+
+
+
+
+
+
+retry_once
types.bool
+
+
+
+
+When `RetryOnce` is true, the test run will be retried once upon failures. This options
+primary purpose is to work around a race condition in phase 5, where the log entry for
+a phase 5 rule may appear after the end marker of the previous test.
+
+
+
+
+
+
+
+isolated
bool
+
+
+
+
+Isolated specifies that the test is expected to trigger a single rule only.
+If the rule triggers any other rule than the (single) one specified in
+expect_ids, the test fill be considered a failure.
+Default: false
+
+
+
+Examples:
+
+
+```yaml
+# Isolated
+isolated: true
+```
+
+
+
+
+
+
+
+
+
+
+## types.Log
+
+
+
+
+
+
+
+
+
+
+expect_ids
[]uint
+
+
+
+
+description: |
+ Expect the given IDs to be contained in the log output.
+ examples:
+ -value: ExampleLog.ExpectIds
+
+
+
+
+
+
+
+no_expect_ids
[]uint
+
+
+
+
+Expect the given IDs _not_ to be contained in the log output.
+
+
+
+Examples:
+
+
+```yaml
+no_expect_ids:
+ - 123456
+```
+
+
+
+
+
+
+
+
+match_regex
string
+
+
+
+
+Expect the regular expression to match log content for the current types.
+
+
+
+Examples:
+
+
+```yaml
+match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+no_match_regex
string
+
+
+
+
+Expect the regular expression to _not_ match log content for the current types.
+
+
+
+Examples:
+
+
+```yaml
+no_match_regex: id[:\s"]*123456
+```
+
+
+
+
+
+
+
+
+
diff --git a/spec/v2.1.0/waf-platform-overrides-schema-v2.1.0.json b/spec/v2.1.0/waf-platform-overrides-schema-v2.1.0.json
new file mode 100755
index 0000000..138a705
--- /dev/null
+++ b/spec/v2.1.0/waf-platform-overrides-schema-v2.1.0.json
@@ -0,0 +1 @@
+{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/coreruleset/ftw-tests-schema/v2/types/overrides/ftw-overrides","$ref":"#/$defs/FTWOverrides","$defs":{"FTWOverrides":{"properties":{"version":{"type":"string"},"meta":{"$ref":"#/$defs/FTWOverridesMeta"},"test_overrides":{"items":{"$ref":"#/$defs/TestOverride"},"type":"array"}},"additionalProperties":false,"type":"object","required":["version","meta","test_overrides"]},"FTWOverridesMeta":{"properties":{"engine":{"type":"string"},"platform":{"type":"string"},"annotations":{"additionalProperties":{"type":"string"},"type":"array"}},"additionalProperties":false,"type":"object","required":["engine","platform","annotations"]},"Log":{"properties":{"expect_ids":{"items":{"type":"integer"},"type":"array"},"no_expect_ids":{"items":{"type":"integer"},"type":"array"},"match_regex":{"type":"string"},"no_match_regex":{"type":"string"}},"additionalProperties":false,"type":"object"},"Output":{"properties":{"status":{"type":"integer"},"response_contains":{"type":"string"},"log_contains":{"type":"string"},"no_log_contains":{"type":"string"},"log":{"$ref":"#/$defs/Log"},"expect_error":{"type":"boolean"},"retry_once":{"type":"boolean"},"isolated":{"type":"boolean"}},"additionalProperties":false,"type":"object"},"TestOverride":{"properties":{"rule_id":{"type":"integer"},"test_ids":{"items":{"type":"integer"},"type":"array"},"stage_ids":{"items":{"type":"integer"},"type":"array"},"reason":{"type":"string"},"retry_once":{"type":"boolean"},"output":{"$ref":"#/$defs/Output"}},"additionalProperties":false,"type":"object","required":["rule_id","reason","output"]}}}
\ No newline at end of file
diff --git a/spec/v2.1.0/waf-tests-schema-v2.1.0.json b/spec/v2.1.0/waf-tests-schema-v2.1.0.json
new file mode 100755
index 0000000..26c1615
--- /dev/null
+++ b/spec/v2.1.0/waf-tests-schema-v2.1.0.json
@@ -0,0 +1 @@
+{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/coreruleset/ftw-tests-schema/v2/types/ftw-test","$ref":"#/$defs/FTWTest","$defs":{"FTWTest":{"properties":{"meta":{"$ref":"#/$defs/FTWTestMeta"},"rule_id":{"type":"integer"},"tests":{"items":{"$ref":"#/$defs/Test"},"type":"array"}},"additionalProperties":false,"type":"object","required":["meta","rule_id","tests"]},"FTWTestMeta":{"properties":{"author":{"type":"string"},"enabled":{"type":"boolean"},"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array"}},"additionalProperties":false,"type":"object"},"Input":{"properties":{"dest_addr":{"type":"string"},"port":{"type":"integer"},"protocol":{"type":"string"},"uri":{"type":"string"},"follow_redirect":{"type":"boolean"},"version":{"type":"string"},"method":{"type":"string"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"data":{"type":"string"},"encoded_data":{"type":"string"},"save_cookie":{"type":"boolean"},"stop_magic":{"type":"boolean"},"autocomplete_headers":{"type":"boolean"},"encoded_request":{"type":"string"},"raw_request":{"type":"string"},"response":{"$ref":"#/$defs/Response"}},"additionalProperties":false,"type":"object"},"Log":{"properties":{"expect_ids":{"items":{"type":"integer"},"type":"array"},"no_expect_ids":{"items":{"type":"integer"},"type":"array"},"match_regex":{"type":"string"},"no_match_regex":{"type":"string"}},"additionalProperties":false,"type":"object"},"Output":{"properties":{"status":{"type":"integer"},"response_contains":{"type":"string"},"log_contains":{"type":"string"},"no_log_contains":{"type":"string"},"log":{"$ref":"#/$defs/Log"},"expect_error":{"type":"boolean"},"retry_once":{"type":"boolean"},"isolated":{"type":"boolean"}},"additionalProperties":false,"type":"object"},"Response":{"properties":{"headers":{"additionalProperties":{"type":"string"},"type":"object"},"status":{"type":"integer"},"body":{"type":"string"},"encoded_body":{"type":"string"},"log_message":{"type":"string"}},"additionalProperties":false,"type":"object"},"Stage":{"properties":{"description":{"type":"string"},"input":{"$ref":"#/$defs/Input"},"output":{"$ref":"#/$defs/Output"}},"additionalProperties":false,"type":"object","required":["input","output"]},"Test":{"properties":{"test_title":{"type":"string"},"test_id":{"type":"integer"},"desc":{"type":"string"},"stages":{"items":{"$ref":"#/$defs/Stage"},"type":"array"},"tags":{"items":{"type":"string"},"type":"array"}},"additionalProperties":false,"type":"object","required":["test_id","stages"]}}}
\ No newline at end of file
diff --git a/types/examples.go b/types/examples.go
index 462b44a..7333a72 100644
--- a/types/examples.go
+++ b/types/examples.go
@@ -61,5 +61,17 @@ var (
}
ReasonExample = "nginx returns 400 when `Content-Length` header is sent in a\n" +
"`Transfer-Encoding: chunked` request."
- ExampleEncodedData = "c29tZXRoaW5nIHdpdGgKbmV3bGluZQo="
+ ExampleEncodedData = "c29tZXRoaW5nIHdpdGgKbmV3bGluZQo="
+ ExampleResponseBody = "{\"aJsonDocument\": [\"in the response\"]}\n"
+ ExampleEncodedResponseBody = "eyJhSnNvbkRvY3VtZW50IjogWyJpbiB0aGUgcmVzcG9uc2UiXX0="
+ ExampleRespone = Response{
+ Status: 502,
+ Headers: map[string]string{
+ "Content-Type": "application/problem+json",
+ "x-mr-burns": "excellent",
+ },
+ Body: ExampleResponseBody,
+ EncodedBody: ExampleEncodedResponseBody,
+ LogMessage: "Response splitting test 1",
+ }
)
diff --git a/types/types.go b/types/types.go
index 0075276..81f22c0 100644
--- a/types/types.go
+++ b/types/types.go
@@ -231,7 +231,7 @@ type Input struct {
Method *string `yaml:"method,omitempty" json:"method,omitempty" koanf:"method,omitempty"`
// description: |
- // Method allows you to declare headers that the test should send.
+ // Headers allows you to declare headers that the test should send.
// examples:
// - name: Headers
// value: ExampleHeaders
@@ -293,9 +293,57 @@ type Input struct {
//
// Deprecated: use `encoded_request`
RAWRequest string `yaml:"raw_request,omitempty" json:"raw_request,omitempty" koanf:"raw_request,omitempty"`
+
+ // description: |
+ // Response describes a response from the web server that a WAF is expected to analyse.
+ // Note: This functionality requires a backend that can send the specified request to the
+ // reverse proxy. Currently, only Albedo (https://github.com/coreruleset/albedo) is supported.
+ // example:
+ // - name Response
+ // value: ExampleResponse
+ Response Response `yaml:"response,omitempty" json:"response,omitempty" koanf:"response,omitempty"`
+}
+
+type Response struct {
+ // description: |
+ // Headers defines the headers the response will carry.
+ // examples:
+ // - name: Headers
+ // value: ExampleHeaders
+ Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty" koanf:"headers,omitempty"`
+
+ // description: |
+ // Status describes the HTTP status code of the response. Defaults to `200` if omitted.
+ // examples:
+ // - name: Status
+ // value: 302
+ Status int `yaml:"status,omitempty" json:"status,omitempty" koanf:"status,omitempty"`
+
+ // description: |
+ // Body defines the body of the response as a plain string.
+ // examples:
+ // - name: Body
+ // value: ExampleResponseBody
+ Body string `yaml:"body,omitempty" json:"body,omitempty" koanf:"body,omitempty"`
+
+ // description: |
+ // EncodedBody defines the body of the response as a base64 encoded string. This is useful if the response
+ // needs to contain non-printable characters.
+ // examples:
+ // - name: EncodedBody
+ // value: ExampleEncodedResponseBody
+ EncodedBody string `yaml:"encoded_body,omitempty" json:"encoded_body,omitempty" koanf:"encoded_body,omitempty"`
+
+ // description: |
+ // LogMessage specifies a message to be printed in the log of the backend server that sends the response.
+ // This can be helpful when debugging, to match resopnses sent by the backend to test executions.
+ // examples:
+ // - name: LogMessage
+ // value: "\"Response splitting test 1\""
+ LogMessage string `yaml:"log_message,omitempty" json:"log_message,omitempty" koanf:"log_message,omitempty"`
}
-// Output is the response expected from the test
+// Output defines the expectations of a test
type Output struct {
// description: |
// Status describes the HTTP status code expected in the response.
diff --git a/types/types_test.go b/types/types_test.go
index 29c7156..9d28ee7 100644
--- a/types/types_test.go
+++ b/types/types_test.go
@@ -42,7 +42,6 @@ tests:
response_contains: ""
log_contains: "nothing"
no_log_contains: "everything"
- - stages:
- input:
dest_addr: "127.0.0.1"
port: 80
@@ -50,6 +49,15 @@ tests:
headers:
User-Agent: "FTW Schema Tests"
Host: "localhost"
+ response:
+ status: 502
+ headers:
+ Content-Type: "application/problem+json"
+ x-mr-burns: excellent
+ body: |
+ {"aJsonDocument": ["in the response"]}
+ encoded_body: eyJhSnNvbkRvY3VtZW50IjogWyJpbiB0aGUgcmVzcG9uc2UiXX0=
+ log_message: Response splitting test 1
output:
status: 200
`
@@ -72,10 +80,6 @@ var ftwTest = &FTWTest{
Input: ExampleInput,
Output: ExampleOutput,
},
- },
- },
- {
- Stages: []Stage{
{
Input: Input{
DestAddr: helpers.StrPtr("127.0.0.1"),
@@ -85,6 +89,7 @@ var ftwTest = &FTWTest{
"User-Agent": "FTW Schema Tests",
"Host": "localhost",
},
+ Response: ExampleRespone,
},
Output: Output{
Status: 200,
@@ -122,9 +127,26 @@ func TestUnmarshalFTWTest(t *testing.T) {
assertions.Equal(expectedStage.Input.Method, stage.Input.Method)
assertions.Len(stage.Input.Headers, len(expectedStage.Input.Headers))
- for k, header := range stage.Input.Headers {
- expectedHeader := expectedStage.Input.Headers[k]
- assertions.Equal(expectedHeader, header)
+ for name, value := range stage.Input.Headers {
+ assertions.Contains(expectedStage.Input.Headers, name)
+ assertions.Equal(expectedStage.Input.Headers[name], value)
+ }
+
+ response := stage.Input.Response
+ if j == 1 {
+ assertions.NotNil(response)
+ assertions.Equal(502, response.Status)
+ assertions.Equal("{\"aJsonDocument\": [\"in the response\"]}\n", response.Body)
+ assertions.Equal("eyJhSnNvbkRvY3VtZW50IjogWyJpbiB0aGUgcmVzcG9uc2UiXX0=", response.EncodedBody)
+ assertions.Equal("Response splitting test 1", response.LogMessage)
+ assertions.Len(response.Headers, len(expectedStage.Input.Headers))
+
+ for name, value := range stage.Input.Headers {
+ assertions.Contains(expectedStage.Input.Headers, name)
+ assertions.Equal(expectedStage.Input.Headers[name], value)
+ }
+ } else {
+ assertions.Equal(response, Response{})
}
assertions.Equal(expectedStage.Output.NoLogContains, stage.Output.NoLogContains)