diff --git a/src/condition/lfi_detector.hpp b/src/condition/lfi_detector.hpp index 2b7262ae5..5197ea75e 100644 --- a/src/condition/lfi_detector.hpp +++ b/src/condition/lfi_detector.hpp @@ -12,6 +12,7 @@ namespace ddwaf { class lfi_detector : public base_impl { public: + static constexpr unsigned version = 1; static constexpr std::array param_names{"resource", "params"}; explicit lfi_detector(std::vector args, const object_limits &limits = {}) diff --git a/src/condition/shi_detector.hpp b/src/condition/shi_detector.hpp index 6ebcafe7a..99b4b11b1 100644 --- a/src/condition/shi_detector.hpp +++ b/src/condition/shi_detector.hpp @@ -13,6 +13,7 @@ namespace ddwaf { class shi_detector : public base_impl { public: + static constexpr unsigned version = 1; static constexpr std::array param_names{"resource", "params"}; explicit shi_detector(std::vector args, const object_limits &limits = {}); diff --git a/src/condition/sqli_detector.hpp b/src/condition/sqli_detector.hpp index 71340116d..3f0714eb8 100644 --- a/src/condition/sqli_detector.hpp +++ b/src/condition/sqli_detector.hpp @@ -13,6 +13,7 @@ namespace ddwaf { class sqli_detector : public base_impl { public: + static constexpr unsigned version = 2; static constexpr std::array param_names{"resource", "params", "db_type"}; explicit sqli_detector(std::vector args, const object_limits &limits = {}) diff --git a/src/condition/ssrf_detector.hpp b/src/condition/ssrf_detector.hpp index 17d46640e..6ac5ef686 100644 --- a/src/condition/ssrf_detector.hpp +++ b/src/condition/ssrf_detector.hpp @@ -13,6 +13,7 @@ namespace ddwaf { class ssrf_detector : public base_impl { public: + static constexpr unsigned version = 1; static constexpr std::array param_names{"resource", "params"}; explicit ssrf_detector(std::vector args, const object_limits &limits = {}); diff --git a/src/exception.hpp b/src/exception.hpp index bf40556d6..f5185fe3e 100644 --- a/src/exception.hpp +++ b/src/exception.hpp @@ -10,6 +10,8 @@ #include #include +#include "fmt/format.h" + namespace ddwaf { class exception : public std::exception { @@ -27,6 +29,15 @@ class unsupported_version : public exception { unsupported_version() : exception(std::string()){}; }; +class unsupported_operator_version : public exception { +public: + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + unsupported_operator_version(std::string_view name, unsigned expected, unsigned current) + : exception(ddwaf::fmt::format( + "unsupported operator version {}@{}, current {}@{}", name, expected, name, current)) + {} +}; + class parsing_error : public exception { public: explicit parsing_error(const std::string &what) : exception(what) {} diff --git a/src/interface.cpp b/src/interface.cpp index d5a79d839..90467821e 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -257,7 +257,7 @@ void ddwaf_context_destroy(ddwaf_context context) } } -const char *ddwaf_get_version() { return LIBDDWAF_VERSION; } +const char *ddwaf_get_version() { return ddwaf::current_version.cstring(); } bool ddwaf_set_log_cb(ddwaf_log_cb cb, DDWAF_LOG_LEVEL min_level) { diff --git a/src/parameter.cpp b/src/parameter.cpp index 5fb36b70f..69ddd35ee 100644 --- a/src/parameter.cpp +++ b/src/parameter.cpp @@ -16,6 +16,7 @@ #include "ddwaf.h" #include "exception.hpp" #include "parameter.hpp" +#include "semver.hpp" #include "utils.hpp" namespace { @@ -306,4 +307,13 @@ parameter::operator std::unordered_map() const return data; } +parameter::operator semantic_version() const +{ + if (type != DDWAF_OBJ_STRING || stringValue == nullptr) { + throw bad_cast("string", strtype(type)); + } + + return semantic_version{{stringValue, static_cast(nbEntries)}}; +} + } // namespace ddwaf diff --git a/src/parameter.hpp b/src/parameter.hpp index d00cdf9d6..0fb9fb3c7 100644 --- a/src/parameter.hpp +++ b/src/parameter.hpp @@ -14,6 +14,7 @@ #include "ddwaf.h" #include "exception.hpp" +#include "semver.hpp" namespace ddwaf { @@ -47,6 +48,7 @@ class parameter : public ddwaf_object { explicit operator std::vector() const; explicit operator std::vector() const; explicit operator std::unordered_map() const; + explicit operator semantic_version() const; ~parameter() = default; }; @@ -87,4 +89,8 @@ template <> struct parameter_traits static const char *name() { return "std::unordered_map"; } }; +template <> struct parameter_traits { + static const char *name() { return "semantic_version"; } +}; + } // namespace ddwaf diff --git a/src/parser/exclusion_parser.cpp b/src/parser/exclusion_parser.cpp index 1da8125db..7b6db9e2e 100644 --- a/src/parser/exclusion_parser.cpp +++ b/src/parser/exclusion_parser.cpp @@ -20,7 +20,9 @@ #include "parser/common.hpp" #include "parser/parser.hpp" #include "parser/specification.hpp" +#include "semver.hpp" #include "utils.hpp" +#include "version.hpp" namespace ddwaf::parser::v2 { @@ -124,6 +126,16 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio continue; } + // Check version compatibility and fail without diagnostic + auto min_version{at(node, "min_version", semantic_version::min())}; + auto max_version{at(node, "max_version", semantic_version::max())}; + if (min_version > current_version || max_version < current_version) { + DDWAF_DEBUG("Skipping filter '{}': version required between [{}, {}], current {}", + id, min_version, max_version, current_version); + info.add_skipped(id); + continue; + } + if (node.find("inputs") != node.end()) { auto filter = parse_input_filter(node, addresses, filter_data_ids, limits); filters.ids.emplace(id); @@ -137,6 +149,9 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio info.add_loaded(id); add_addresses_to_info(addresses, info); + } catch (const unsupported_operator_version &e) { + DDWAF_WARN("Skipping filter '{}': {}", id, e.what()); + info.add_skipped(id); } catch (const std::exception &e) { if (id.empty()) { id = index_to_id(i); diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index bcb55ad69..1d0faef5e 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -111,8 +111,10 @@ std::vector parse_arguments(const parameter::map ¶ms, d } template -auto build_condition(auto operator_name, auto ¶ms, auto &data_ids_to_type, auto source, - auto &transformers, auto &addresses, auto &limits) +auto build_condition(std::string_view operator_name, const parameter::map ¶ms, + std::unordered_map &data_ids_to_type, data_source source, + const std::vector &transformers, address_container &addresses, + const object_limits &limits) { auto [data_id, matcher] = parse_matcher(operator_name, params); @@ -124,6 +126,20 @@ auto build_condition(auto operator_name, auto ¶ms, auto &data_ids_to_type, a return std::make_unique(std::move(matcher), data_id, std::move(arguments), limits); } +template +auto build_versioned_condition(std::string_view operator_name, unsigned version, + const parameter::map ¶ms, data_source source, + const std::vector &transformers, address_container &addresses, + const object_limits &limits) +{ + if (version > Condition::version) { + throw unsupported_operator_version(operator_name, version, Condition::version); + } + + auto arguments = parse_arguments(params, source, transformers, addresses, limits); + return std::make_unique(std::move(arguments), limits); +} + } // namespace std::shared_ptr parse_expression(const parameter::vector &conditions_array, @@ -138,22 +154,30 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto operator_name = at(root, "operator"); auto params = at(root, "parameters"); + // Exploit Prevention Operators may have a single-digit version + unsigned version = 0; + auto version_idx = operator_name.find("@v"); + if (version_idx != std::string_view::npos) { + auto version_str = operator_name.substr(version_idx + 2); + auto [res, value] = from_string(version_str); + if (res) { + version = value; + } + operator_name = operator_name.substr(0, version_idx); + } + if (operator_name == "lfi_detector") { - auto arguments = - parse_arguments(params, source, transformers, addresses, limits); - conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + conditions.emplace_back(build_versioned_condition( + operator_name, version, params, source, transformers, addresses, limits)); } else if (operator_name == "ssrf_detector") { - auto arguments = - parse_arguments(params, source, transformers, addresses, limits); - conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + conditions.emplace_back(build_versioned_condition( + operator_name, version, params, source, transformers, addresses, limits)); } else if (operator_name == "sqli_detector") { - auto arguments = - parse_arguments(params, source, transformers, addresses, limits); - conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + conditions.emplace_back(build_versioned_condition( + operator_name, version, params, source, transformers, addresses, limits)); } else if (operator_name == "shi_detector") { - auto arguments = - parse_arguments(params, source, transformers, addresses, limits); - conditions.emplace_back(std::make_unique(std::move(arguments), limits)); + conditions.emplace_back(build_versioned_condition( + operator_name, version, params, source, transformers, addresses, limits)); } else if (operator_name == "exists") { auto arguments = parse_arguments(params, source, transformers, addresses, limits); diff --git a/src/parser/processor_parser.cpp b/src/parser/processor_parser.cpp index f8a7c3e04..a720624a8 100644 --- a/src/parser/processor_parser.cpp +++ b/src/parser/processor_parser.cpp @@ -20,7 +20,9 @@ #include "processor/base.hpp" #include "processor/extract_schema.hpp" #include "processor/fingerprint.hpp" +#include "semver.hpp" #include "utils.hpp" +#include "version.hpp" namespace ddwaf::parser::v2 { @@ -81,6 +83,17 @@ processor_container parse_processors( continue; } + // Check version compatibility and fail without diagnostic + auto min_version{at(node, "min_version", semantic_version::min())}; + auto max_version{at(node, "max_version", semantic_version::max())}; + if (min_version > current_version || max_version < current_version) { + DDWAF_DEBUG( + "Skipping processor '{}': version required between [{}, {}], current {}", id, + min_version, max_version, current_version); + info.add_skipped(id); + continue; + } + processor_type type; auto generator_id = at(node, "generator"); if (generator_id == "extract_schema") { @@ -152,7 +165,9 @@ processor_container parse_processors( processors.post.emplace_back(processor_builder{type, std::move(id), std::move(expr), std::move(mappings), std::move(scanners), eval, output}); } - + } catch (const unsupported_operator_version &e) { + DDWAF_WARN("Skipping processor '{}': {}", id, e.what()); + info.add_skipped(id); } catch (const std::exception &e) { if (id.empty()) { id = index_to_id(i); diff --git a/src/parser/rule_parser.cpp b/src/parser/rule_parser.cpp index 9d142c04c..3d2eadbe7 100644 --- a/src/parser/rule_parser.cpp +++ b/src/parser/rule_parser.cpp @@ -17,8 +17,10 @@ #include "parser/parser.hpp" #include "parser/specification.hpp" #include "rule.hpp" +#include "semver.hpp" #include "transformer/base.hpp" #include "utils.hpp" +#include "version.hpp" namespace ddwaf::parser::v2 { @@ -71,24 +73,37 @@ rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info rule_spec_container rules; for (unsigned i = 0; i < rule_array.size(); ++i) { const auto &rule_param = rule_array[i]; - auto rule_map = static_cast(rule_param); + auto node = static_cast(rule_param); std::string id; try { address_container addresses; - id = at(rule_map, "id"); + id = at(node, "id"); if (rules.find(id) != rules.end()) { DDWAF_WARN("Duplicate rule {}", id); info.add_failed(id, "duplicate rule"); continue; } - auto rule = parse_rule(rule_map, rule_data_ids, limits, source, addresses); + // Check version compatibility and fail without diagnostic + auto min_version{at(node, "min_version", semantic_version::min())}; + auto max_version{at(node, "max_version", semantic_version::max())}; + if (min_version > current_version || max_version < current_version) { + DDWAF_DEBUG("Skipping rule '{}': version required between [{}, {}], current {}", id, + min_version, max_version, current_version); + info.add_skipped(id); + continue; + } + + auto rule = parse_rule(node, rule_data_ids, limits, source, addresses); DDWAF_DEBUG("Parsed rule {}", id); info.add_loaded(id); add_addresses_to_info(addresses, info); rules.emplace(std::move(id), std::move(rule)); + } catch (const unsupported_operator_version &e) { + DDWAF_WARN("Skipping rule '{}': {}", id, e.what()); + info.add_skipped(id); } catch (const std::exception &e) { if (id.empty()) { id = index_to_id(i); diff --git a/src/parser/scanner_parser.cpp b/src/parser/scanner_parser.cpp index 6255b816c..6495e6797 100644 --- a/src/parser/scanner_parser.cpp +++ b/src/parser/scanner_parser.cpp @@ -20,6 +20,8 @@ #include "parser/matcher_parser.hpp" #include "parser/parser.hpp" #include "scanner.hpp" +#include "semver.hpp" +#include "version.hpp" namespace ddwaf::parser::v2 { @@ -55,6 +57,16 @@ indexer parse_scanners(parameter::vector &scanner_array, base_sec continue; } + // Check version compatibility and fail without diagnostic + auto min_version{at(node, "min_version", semantic_version::min())}; + auto max_version{at(node, "max_version", semantic_version::max())}; + if (min_version > current_version || max_version < current_version) { + DDWAF_DEBUG("Skipping scanner '{}': version required between [{}, {}], current {}", + id, min_version, max_version, current_version); + info.add_skipped(id); + continue; + } + std::unordered_map tags; for (auto &[key, value] : at(node, "tags")) { try { diff --git a/src/ruleset_info.cpp b/src/ruleset_info.cpp index 01cd3e7ad..464b8a1fe 100644 --- a/src/ruleset_info.cpp +++ b/src/ruleset_info.cpp @@ -58,6 +58,13 @@ void ruleset_info::section_info::add_failed(std::string_view id, std::string_vie ddwaf_object_array_add(&failed_, &id_str); } +void ruleset_info::section_info::add_skipped(std::string_view id) +{ + ddwaf_object id_str; + ddwaf_object_stringl(&id_str, id.data(), id.size()); + ddwaf_object_array_add(&skipped_, &id_str); +} + void ruleset_info::section_info::add_required_address(std::string_view address) { if (!required_addresses_set_.contains(address)) { diff --git a/src/ruleset_info.hpp b/src/ruleset_info.hpp index ea87dc9da..dc5e34c2e 100644 --- a/src/ruleset_info.hpp +++ b/src/ruleset_info.hpp @@ -30,6 +30,7 @@ class base_ruleset_info { virtual void set_error(std::string_view error) = 0; virtual void add_loaded(std::string_view id) = 0; virtual void add_failed(std::string_view id, std::string_view error) = 0; + virtual void add_skipped(std::string_view id) = 0; virtual void add_required_address(std::string_view address) = 0; virtual void add_optional_address(std::string_view address) = 0; }; @@ -61,6 +62,7 @@ class null_ruleset_info : public base_ruleset_info { void set_error(std::string_view /*error*/) override {} void add_loaded(std::string_view /*id*/) override {} void add_failed(std::string_view /*id*/, std::string_view /*error*/) override {} + void add_skipped(std::string_view /*id*/) override {} void add_required_address(std::string_view /*address*/) override {} void add_optional_address(std::string_view /*address*/) override {} }; @@ -91,6 +93,7 @@ class ruleset_info : public base_ruleset_info { { ddwaf_object_array(&loaded_); ddwaf_object_array(&failed_); + ddwaf_object_array(&skipped_); ddwaf_object_map(&errors_); ddwaf_object_array(&required_addresses_); ddwaf_object_array(&optional_addresses_); @@ -100,6 +103,7 @@ class ruleset_info : public base_ruleset_info { { ddwaf_object_free(&loaded_); ddwaf_object_free(&failed_); + ddwaf_object_free(&skipped_); ddwaf_object_free(&errors_); ddwaf_object_free(&required_addresses_); ddwaf_object_free(&optional_addresses_); @@ -113,6 +117,7 @@ class ruleset_info : public base_ruleset_info { void set_error(std::string_view error) override { error_ = error; } void add_loaded(std::string_view id) override; void add_failed(std::string_view id, std::string_view error) override; + void add_skipped(std::string_view id) override; void add_required_address(std::string_view address) override; void add_optional_address(std::string_view address) override; @@ -128,6 +133,7 @@ class ruleset_info : public base_ruleset_info { } else { ddwaf_object_map_add(&output, "loaded", &loaded_); ddwaf_object_map_add(&output, "failed", &failed_); + ddwaf_object_map_add(&output, "skipped", &skipped_); ddwaf_object_map_add(&output, "errors", &errors_); if (!required_addresses_set_.empty() || !optional_addresses_set_.empty()) { @@ -140,6 +146,7 @@ class ruleset_info : public base_ruleset_info { ddwaf_object_invalid(&loaded_); ddwaf_object_invalid(&failed_); + ddwaf_object_invalid(&skipped_); ddwaf_object_invalid(&errors_); error_obj_cache_.clear(); @@ -158,6 +165,8 @@ class ruleset_info : public base_ruleset_info { ddwaf_object loaded_{}; /** Array of failed elements */ ddwaf_object failed_{}; + /** Array of skipped elements */ + ddwaf_object skipped_{}; /** Map from an error string to an array of all the ids for which * that error was raised. {error: [ids]} **/ ddwaf_object errors_{}; diff --git a/src/semver.hpp b/src/semver.hpp new file mode 100644 index 000000000..5b85cf560 --- /dev/null +++ b/src/semver.hpp @@ -0,0 +1,118 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#pragma once + +#include +#include +#include +#include + +#include "utils.hpp" + +namespace ddwaf { +class semantic_version { +public: + explicit semantic_version(std::string_view version) : str_(version) + { + // The expected version string is: xxx.yyy.zzz[-label] + // We only try to extract xxx, yyy and zzz, while discarding the label. + // Each element can be 1 to 3 digits long, but no longer. + // Any deviation from this will be rejected. + + // Major + std::size_t start = 0; + auto end = version.find('.'); + if (end == std::string_view::npos) { + throw std::invalid_argument("invalid version syntax"); + } + auto major_str = version.substr(start, end - start); + if (major_str.empty() || major_str.size() > 3 || !parse_number(major_str, major_)) { + throw std::invalid_argument("invalid major version: " + std::string{major_str}); + } + + // Minor + start = end + 1; + end = version.find('.', start); + if (end == std::string_view::npos) { + throw std::invalid_argument("invalid version syntax"); + } + auto minor_str = version.substr(start, end - start); + if (minor_str.empty() || minor_str.size() > 3 || !parse_number(minor_str, minor_)) { + throw std::invalid_argument("invalid minor version: " + std::string{minor_str}); + } + + // Patch + start = end + 1; + end = version.find('-', start); + auto patch_str = version.substr(start, end - start); + if (patch_str.empty() || patch_str.size() > 3 || !parse_number(patch_str, patch_)) { + throw std::invalid_argument("invalid patch version: " + std::string{patch_str}); + } + + number_ = major_ * 1000000 + minor_ * 1000 + patch_; + } + + bool operator==(const semantic_version &other) const noexcept + { + return number_ == other.number_; + } + auto operator<=>(const semantic_version &other) const noexcept + { + return number_ <=> other.number_; + } + + [[nodiscard]] unsigned number() const noexcept { return number_; } + [[nodiscard]] const char *cstring() const noexcept { return str_.c_str(); } + [[nodiscard]] std::string_view string() const noexcept { return str_; } + [[nodiscard]] uint16_t major() const noexcept { return major_; } + [[nodiscard]] uint16_t minor() const noexcept { return minor_; } + [[nodiscard]] uint16_t patch() const noexcept { return patch_; } + + static semantic_version max() + { + static semantic_version v{"999.999.999", 999, 999, 999, 999999999}; + return v; + } + + static semantic_version min() + { + static semantic_version v{"0.0.0", 0, 0, 0, 0}; + return v; + } + +protected: + semantic_version( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + std::string_view str, uint16_t major, uint16_t minor, uint16_t patch, uint32_t number) + : str_(str), major_(major), minor_(minor), patch_(patch), number_(number) + {} + + static bool parse_number(std::string_view str, uint16_t &output) + { + if (auto [res, value] = from_string(str); res) { + output = value; + return true; + } + return false; + } + + std::string str_; + uint16_t major_{0}; + uint16_t minor_{0}; + uint16_t patch_{0}; + uint32_t number_{0}; +}; + +template <> struct fmt::formatter : fmt::formatter { + // Use the parse method from the base class formatter + template auto format(semantic_version &v, FormatContext &ctx) + { + return fmt::formatter::format(v.string(), ctx); + } +}; + +} // namespace ddwaf diff --git a/src/version.hpp.in b/src/version.hpp.in index 2e0913583..999a90c26 100644 --- a/src/version.hpp.in +++ b/src/version.hpp.in @@ -6,4 +6,10 @@ #pragma once -constexpr const char *LIBDDWAF_VERSION = "${PROJECT_VERSION}"; +#include "semver.hpp" + +namespace ddwaf { + +static semantic_version current_version{"${PROJECT_VERSION}"}; + +} // namespace ddwaf diff --git a/tests/integration/diagnostics/test.cpp b/tests/integration/diagnostics/test.cpp index b8e9f84d1..e1554dc5b 100644 --- a/tests/integration/diagnostics/test.cpp +++ b/tests/integration/diagnostics/test.cpp @@ -35,7 +35,7 @@ TEST(TestDiagnosticsIntegration, Rules) EXPECT_STR(version, "5.4.2"); auto rules = ddwaf::parser::at(root_map, "rules"); - EXPECT_EQ(rules.size(), 4); + EXPECT_EQ(rules.size(), 5); auto loaded = ddwaf::parser::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 4); @@ -47,6 +47,9 @@ TEST(TestDiagnosticsIntegration, Rules) auto failed = ddwaf::parser::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); + auto skipped = ddwaf::parser::at(rules, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); @@ -70,6 +73,163 @@ TEST(TestDiagnosticsIntegration, Rules) ddwaf_destroy(handle); } +TEST(TestDiagnosticsIntegration, RulesWithMinVersion) +{ + auto rule = read_file("rules_min_version.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_object diagnostics; + ddwaf_handle handle = ddwaf_init(&rule, &config, &diagnostics); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root = diagnostics; + auto root_map = static_cast(root); + + auto version = ddwaf::parser::at(root_map, "ruleset_version"); + EXPECT_STR(version, "5.4.2"); + + auto rules = ddwaf::parser::at(root_map, "rules"); + EXPECT_EQ(rules.size(), 5); + + auto loaded = ddwaf::parser::at(rules, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("rule1")); + + auto failed = ddwaf::parser::at(rules, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(rules, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("rule2")); + + auto errors = ddwaf::parser::at(rules, "errors"); + EXPECT_EQ(errors.size(), 0); + + auto addresses = ddwaf::parser::at(rules, "addresses"); + EXPECT_EQ(addresses.size(), 2); + + auto required = ddwaf::parser::at(addresses, "required"); + EXPECT_EQ(required.size(), 1); + EXPECT_TRUE(required.contains("value1")); + + auto optional = ddwaf::parser::at(addresses, "optional"); + EXPECT_EQ(optional.size(), 0); + + ddwaf_object_free(&root); + } + + ddwaf_destroy(handle); +} + +TEST(TestDiagnosticsIntegration, RulesWithMaxVersion) +{ + auto rule = read_file("rules_max_version.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_object diagnostics; + ddwaf_handle handle = ddwaf_init(&rule, &config, &diagnostics); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root = diagnostics; + auto root_map = static_cast(root); + + auto version = ddwaf::parser::at(root_map, "ruleset_version"); + EXPECT_STR(version, "5.4.2"); + + auto rules = ddwaf::parser::at(root_map, "rules"); + EXPECT_EQ(rules.size(), 5); + + auto loaded = ddwaf::parser::at(rules, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("rule1")); + + auto failed = ddwaf::parser::at(rules, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(rules, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("rule2")); + + auto errors = ddwaf::parser::at(rules, "errors"); + EXPECT_EQ(errors.size(), 0); + + auto addresses = ddwaf::parser::at(rules, "addresses"); + EXPECT_EQ(addresses.size(), 2); + + auto required = ddwaf::parser::at(addresses, "required"); + EXPECT_EQ(required.size(), 1); + EXPECT_TRUE(required.contains("value1")); + + auto optional = ddwaf::parser::at(addresses, "optional"); + EXPECT_EQ(optional.size(), 0); + + ddwaf_object_free(&root); + } + + ddwaf_destroy(handle); +} + +TEST(TestDiagnosticsIntegration, RulesWithMinMaxVersion) +{ + auto rule = read_file("rules_min_max_version.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_object diagnostics; + ddwaf_handle handle = ddwaf_init(&rule, &config, &diagnostics); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root = diagnostics; + auto root_map = static_cast(root); + + auto version = ddwaf::parser::at(root_map, "ruleset_version"); + EXPECT_STR(version, "5.4.2"); + + auto rules = ddwaf::parser::at(root_map, "rules"); + EXPECT_EQ(rules.size(), 5); + + auto loaded = ddwaf::parser::at(rules, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("rule1")); + + auto failed = ddwaf::parser::at(rules, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(rules, "skipped"); + EXPECT_EQ(skipped.size(), 2); + EXPECT_TRUE(skipped.contains("rule2")); + EXPECT_TRUE(skipped.contains("rule3")); + + auto errors = ddwaf::parser::at(rules, "errors"); + EXPECT_EQ(errors.size(), 0); + + auto addresses = ddwaf::parser::at(rules, "addresses"); + EXPECT_EQ(addresses.size(), 2); + + auto required = ddwaf::parser::at(addresses, "required"); + EXPECT_EQ(required.size(), 1); + EXPECT_TRUE(required.contains("value1")); + + auto optional = ddwaf::parser::at(addresses, "optional"); + EXPECT_EQ(optional.size(), 0); + + ddwaf_object_free(&root); + } + + ddwaf_destroy(handle); +} + TEST(TestDiagnosticsIntegration, RulesWithErrors) { auto rule = read_file("rules_with_errors.yaml", base_dir); @@ -90,12 +250,15 @@ TEST(TestDiagnosticsIntegration, RulesWithErrors) EXPECT_STR(version, "5.4.1"); auto rules = ddwaf::parser::at(root_map, "rules"); - EXPECT_EQ(rules.size(), 4); + EXPECT_EQ(rules.size(), 5); auto loaded = ddwaf::parser::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("rule1")); + auto skipped = ddwaf::parser::at(rules, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto failed = ddwaf::parser::at(rules, "failed"); EXPECT_EQ(failed.size(), 5); EXPECT_TRUE(failed.contains("rule1")); @@ -180,7 +343,7 @@ TEST(TestDiagnosticsIntegration, CustomRules) EXPECT_STR(version, "5.4.3"); auto rules = ddwaf::parser::at(root_map, "custom_rules"); - EXPECT_EQ(rules.size(), 4); + EXPECT_EQ(rules.size(), 5); auto loaded = ddwaf::parser::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 4); @@ -192,6 +355,9 @@ TEST(TestDiagnosticsIntegration, CustomRules) auto failed = ddwaf::parser::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); + auto skipped = ddwaf::parser::at(rules, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); @@ -232,7 +398,7 @@ TEST(TestDiagnosticsIntegration, InputFilter) auto root_map = static_cast(root); auto exclusions = ddwaf::parser::at(root_map, "exclusions"); - EXPECT_EQ(exclusions.size(), 4); + EXPECT_EQ(exclusions.size(), 5); auto loaded = ddwaf::parser::at(exclusions, "loaded"); EXPECT_EQ(loaded.size(), 1); @@ -241,6 +407,9 @@ TEST(TestDiagnosticsIntegration, InputFilter) auto failed = ddwaf::parser::at(exclusions, "failed"); EXPECT_EQ(failed.size(), 0); + auto skipped = ddwaf::parser::at(exclusions, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(exclusions, "errors"); EXPECT_EQ(errors.size(), 0); @@ -279,7 +448,7 @@ TEST(TestDiagnosticsIntegration, RuleData) auto root_map = static_cast(root); auto rule_data = ddwaf::parser::at(root_map, "rules_data"); - EXPECT_EQ(rule_data.size(), 3); + EXPECT_EQ(rule_data.size(), 4); auto loaded = ddwaf::parser::at(rule_data, "loaded"); EXPECT_EQ(loaded.size(), 2); @@ -289,6 +458,9 @@ TEST(TestDiagnosticsIntegration, RuleData) auto failed = ddwaf::parser::at(rule_data, "failed"); EXPECT_EQ(failed.size(), 0); + auto skipped = ddwaf::parser::at(rule_data, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(rule_data, "errors"); EXPECT_EQ(errors.size(), 0); @@ -315,7 +487,7 @@ TEST(TestDiagnosticsIntegration, Processor) auto root_map = static_cast(root); auto processor = ddwaf::parser::at(root_map, "processors"); - EXPECT_EQ(processor.size(), 4); + EXPECT_EQ(processor.size(), 5); auto loaded = ddwaf::parser::at(processor, "loaded"); EXPECT_EQ(loaded.size(), 1); @@ -324,6 +496,9 @@ TEST(TestDiagnosticsIntegration, Processor) auto failed = ddwaf::parser::at(processor, "failed"); EXPECT_EQ(failed.size(), 0); + auto skipped = ddwaf::parser::at(processor, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(processor, "errors"); EXPECT_EQ(errors.size(), 0); diff --git a/tests/integration/diagnostics/yaml/rules_max_version.yaml b/tests/integration/diagnostics/yaml/rules_max_version.yaml new file mode 100644 index 000000000..5eddd16ce --- /dev/null +++ b/tests/integration/diagnostics/yaml/rules_max_version.yaml @@ -0,0 +1,29 @@ +version: '2.1' +metadata: + rules_version: 5.4.2 +rules: + - id: rule1 + name: rule1 + tags: + type: flow1 + category: category1 + confidence: 1 + max_version: 999.0.0 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: ^rule1 + - id: rule2 + name: rule2 + tags: + type: flow2 + category: category2 + max_version: 1.0.0 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: ^rule2 diff --git a/tests/integration/diagnostics/yaml/rules_min_max_version.yaml b/tests/integration/diagnostics/yaml/rules_min_max_version.yaml new file mode 100644 index 000000000..04f081606 --- /dev/null +++ b/tests/integration/diagnostics/yaml/rules_min_max_version.yaml @@ -0,0 +1,44 @@ +version: '2.1' +metadata: + rules_version: 5.4.2 +rules: + - id: rule1 + name: rule1 + tags: + type: flow1 + category: category1 + confidence: 1 + min_version: 0.0.1 + max_version: 999.0.1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: ^rule1 + - id: rule2 + name: rule2 + tags: + type: flow2 + category: category2 + min_version: 999.0.0 + max_version: 999.999.999 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: ^rule2 + - id: rule3 + name: rule3 + tags: + type: flow3 + category: category3 + min_version: 0.0.0 + max_version: 1.19.0 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value3 + regex: ^rule3 diff --git a/tests/integration/diagnostics/yaml/rules_min_version.yaml b/tests/integration/diagnostics/yaml/rules_min_version.yaml new file mode 100644 index 000000000..72b9f08ad --- /dev/null +++ b/tests/integration/diagnostics/yaml/rules_min_version.yaml @@ -0,0 +1,29 @@ +version: '2.1' +metadata: + rules_version: 5.4.2 +rules: + - id: rule1 + name: rule1 + tags: + type: flow1 + category: category1 + confidence: 1 + min_version: 0.0.1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: ^rule1 + - id: rule2 + name: rule2 + tags: + type: flow2 + category: category2 + min_version: 999.0.0 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: ^rule2 diff --git a/tests/interface_test.cpp b/tests/interface_test.cpp index 5593dd3a7..2765e475e 100644 --- a/tests/interface_test.cpp +++ b/tests/interface_test.cpp @@ -21,7 +21,10 @@ TEST(TestInterface, Empty) ddwaf_object_free(&rule); } -TEST(TestInterface, ddwaf_get_version) { EXPECT_STREQ(ddwaf_get_version(), LIBDDWAF_VERSION); } +TEST(TestInterface, ddwaf_get_version) +{ + EXPECT_STREQ(ddwaf_get_version(), ddwaf::current_version.cstring()); +} TEST(TestInterface, HandleBad) { diff --git a/tests/parameter_test.cpp b/tests/parameter_test.cpp index bdf2701e0..86392e907 100644 --- a/tests/parameter_test.cpp +++ b/tests/parameter_test.cpp @@ -491,4 +491,36 @@ TEST(TestParameter, ToStringViewSet) } } +TEST(TestParameter, ToSemanticVersion) +{ + { + ddwaf_object root; + ddwaf_object_string(&root, "1.2.3"); + + auto value = static_cast(ddwaf::parameter(root)); + EXPECT_EQ(value.number(), 1002003); + + ddwaf_object_free(&root); + } + + { + ddwaf_object root; + ddwaf_object_string(&root, "1.2.3"); + // NOLINTNEXTLINE(hicpp-no-malloc) + free((void *)root.stringValue); + root.stringValue = nullptr; + + ddwaf::parameter param = root; + EXPECT_THROW(param.operator semantic_version(), ddwaf::bad_cast); + } + + { + ddwaf_object root; + ddwaf_object_unsigned(&root, 3); + + ddwaf::parameter param = root; + EXPECT_THROW(param.operator semantic_version(), ddwaf::bad_cast); + } +} + } // namespace diff --git a/tests/parser_v2_input_filters_test.cpp b/tests/parser_v2_input_filters_test.cpp index 10224bfe8..c0c756651 100644 --- a/tests/parser_v2_input_filters_test.cpp +++ b/tests/parser_v2_input_filters_test.cpp @@ -554,4 +554,118 @@ TEST(TestParserV2InputFilters, ParseConditionalMultipleConditions) EXPECT_EQ(target.tags.size(), 0); } +TEST(TestParserV2InputFilters, IncompatibleMinVersion) +{ + ddwaf::object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}], min_version: 99.0.0}])"); + + std::unordered_map data_ids; + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 0); + EXPECT_EQ(filters.input_filters.size(), 0); +} + +TEST(TestParserV2InputFilters, IncompatibleMaxVersion) +{ + ddwaf::object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}], max_version: 0.0.99}])"); + + std::unordered_map data_ids; + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 0); + EXPECT_EQ(filters.input_filters.size(), 0); +} + +TEST(TestParserV2InputFilters, CompatibleVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], min_version: 0.0.99, max_version: 2.0.0}])"); + + std::unordered_map data_ids; + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 0); + EXPECT_EQ(filters.input_filters.size(), 1); +} + } // namespace diff --git a/tests/parser_v2_processors_test.cpp b/tests/parser_v2_processors_test.cpp index 3a7580654..ec8d3bf9f 100644 --- a/tests/parser_v2_processors_test.cpp +++ b/tests/parser_v2_processors_test.cpp @@ -502,4 +502,121 @@ TEST(TestParserV2Processors, ParseDuplicate) } } +TEST(TestParserV2Processors, IncompatibleMinVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, min_version: 99.0.0, evaluate: false, output: true}])"); + + ddwaf::ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + auto processors = parser::v2::parse_processors(array, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(processors.size(), 0); + EXPECT_EQ(processors.pre.size(), 0); + EXPECT_EQ(processors.post.size(), 0); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } +} + +TEST(TestParserV2Processors, IncompatibleMaxVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, max_version: 0.0.99, evaluate: false, output: true}])"); + + ddwaf::ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + auto processors = parser::v2::parse_processors(array, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(processors.size(), 0); + EXPECT_EQ(processors.pre.size(), 0); + EXPECT_EQ(processors.post.size(), 0); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } +} + +TEST(TestParserV2Processors, CompatibleVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, min_version: 0.0.99, max_version: 2.0.0, evaluate: false, output: true}])"); + + ddwaf::ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + auto processors = parser::v2::parse_processors(array, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(processors.size(), 1); + EXPECT_EQ(processors.pre.size(), 0); + EXPECT_EQ(processors.post.size(), 1); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } +} + } // namespace diff --git a/tests/parser_v2_rule_filters_test.cpp b/tests/parser_v2_rule_filters_test.cpp index a94a7e761..c70540d70 100644 --- a/tests/parser_v2_rule_filters_test.cpp +++ b/tests/parser_v2_rule_filters_test.cpp @@ -732,4 +732,122 @@ TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) EXPECT_EQ(filters.rule_filters.size(), 0); EXPECT_EQ(filters.input_filters.size(), 0); } + +TEST(TestParserV2RuleFilters, IncompatibleMinVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], min_version: 99.0.0, on_match: monitor}])"); + + std::unordered_map data_ids; + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 0); + EXPECT_EQ(filters.input_filters.size(), 0); +} + +TEST(TestParserV2RuleFilters, IncompatibleMaxVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], max_version: 0.0.99, on_match: monitor}])"); + + std::unordered_map data_ids; + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 0); + EXPECT_EQ(filters.input_filters.size(), 0); +} + +TEST(TestParserV2RuleFilters, CompatibleVersion) +{ + ddwaf::object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], min_version: 0.0.99, max_version: 2.0.0, on_match: monitor}])"); + + std::unordered_map data_ids; + ddwaf::ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); + ddwaf_object_free(&object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(filters.rule_filters.size(), 1); + EXPECT_EQ(filters.input_filters.size(), 0); +} + } // namespace diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp index a3c894e94..7cca3659d 100644 --- a/tests/parser_v2_rules_test.cpp +++ b/tests/parser_v2_rules_test.cpp @@ -457,4 +457,209 @@ TEST(TestParserV2Rules, NegatedMatcherTooManyParameters) EXPECT_EQ(rules.size(), 0); } +TEST(TestParserV2Rules, SupportedVersionedOperator) +{ + ddwaf::object_limits limits; + limits.max_container_depth = 2; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v2"}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("rsp-930-003")); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(rules.size(), 1); +} + +TEST(TestParserV2Rules, UnsupportedVersionedOperator) +{ + ddwaf::object_limits limits; + limits.max_container_depth = 2; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v3"}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("rsp-930-003")); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(rules.size(), 0); +} + +TEST(TestParserV2Rules, IncompatibleMinVersion) +{ + ddwaf::object_limits limits; + limits.max_container_depth = 2; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 99.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("1")); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(rules.size(), 0); +} + +TEST(TestParserV2Rules, IncompatibleMaxVersion) +{ + ddwaf::object_limits limits; + limits.max_container_depth = 2; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, max_version: 0.0.99, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("1")); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(rules.size(), 0); +} + +TEST(TestParserV2Rules, CompatibleVersion) +{ + ddwaf::object_limits limits; + limits.max_container_depth = 2; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 0.0.99, max_version: 2.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("1")); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(rules.size(), 1); +} + } // namespace diff --git a/tests/parser_v2_scanner_test.cpp b/tests/parser_v2_scanner_test.cpp index a1430c72d..f0477a8fb 100644 --- a/tests/parser_v2_scanner_test.cpp +++ b/tests/parser_v2_scanner_test.cpp @@ -44,8 +44,7 @@ TEST(TestParserV2Scanner, ParseKeyOnlyScanner) EXPECT_EQ(scanners.size(), 1); EXPECT_NE(scanners.find_by_id("ecd"), nullptr); - auto *scnr = scanners.find_by_id("ecd"); - ; + const auto *scnr = scanners.find_by_id("ecd"); EXPECT_STREQ(scnr->get_id().data(), "ecd"); std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; EXPECT_EQ(scnr->get_tags(), tags); @@ -93,8 +92,7 @@ TEST(TestParserV2Scanner, ParseValueOnlyScanner) EXPECT_EQ(scanners.size(), 1); EXPECT_NE(scanners.find_by_id("ecd"), nullptr); - auto *scnr = scanners.find_by_id("ecd"); - ; + const auto *scnr = scanners.find_by_id("ecd"); EXPECT_STREQ(scnr->get_id().data(), "ecd"); std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; EXPECT_EQ(scnr->get_tags(), tags); @@ -142,8 +140,7 @@ TEST(TestParserV2Scanner, ParseKeyValueScanner) EXPECT_EQ(scanners.size(), 1); EXPECT_NE(scanners.find_by_id("ecd"), nullptr); - auto *scnr = scanners.find_by_id("ecd"); - ; + const auto *scnr = scanners.find_by_id("ecd"); EXPECT_STREQ(scnr->get_id().data(), "ecd"); std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; EXPECT_EQ(scnr->get_tags(), tags); @@ -539,4 +536,110 @@ TEST(TestParserV2Scanner, ParseRuleDataID) EXPECT_EQ(scanners.size(), 0); } + +TEST(TestParserV2Scanner, IncompatibleMinVersion) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "min_version": "99.0.0"}])"); + auto scanners_array = static_cast(parameter(definition)); + + ddwaf::ruleset_info::section_info section; + auto scanners = parser::v2::parse_scanners(scanners_array, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("ecd")); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(scanners.size(), 0); +} + +TEST(TestParserV2Scanner, IncompatibleMaxVersion) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "max_version": "0.0.99"}])"); + auto scanners_array = static_cast(parameter(definition)); + + ddwaf::ruleset_info::section_info section; + auto scanners = parser::v2::parse_scanners(scanners_array, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("ecd")); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(scanners.size(), 0); +} + +TEST(TestParserV2Scanner, CompatibleVersion) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "min_version": "0.0.99", "max_version": "2.0.0"}])"); + auto scanners_array = static_cast(parameter(definition)); + + ddwaf::ruleset_info::section_info section; + auto scanners = parser::v2::parse_scanners(scanners_array, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("ecd")); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(scanners.size(), 1); +} + } // namespace diff --git a/tests/ruleset_info_test.cpp b/tests/ruleset_info_test.cpp index 4071e8356..4a31fa379 100644 --- a/tests/ruleset_info_test.cpp +++ b/tests/ruleset_info_test.cpp @@ -59,7 +59,7 @@ TEST(TestRulesetInfo, ValidRulesetInfo) {"rules", "first"}, {"exclusions", "second"}, {"rules_override", "third"}}; for (auto &[key, value] : kv) { auto section = ddwaf::parser::at(root_map, key); - EXPECT_EQ(section.size(), 3); + EXPECT_EQ(section.size(), 4); auto loaded = ddwaf::parser::at(section, "loaded"); EXPECT_EQ(loaded.size(), 1); @@ -69,6 +69,9 @@ TEST(TestRulesetInfo, ValidRulesetInfo) auto failed = ddwaf::parser::at(section, "failed"); EXPECT_EQ(failed.size(), 0); + auto skipped = ddwaf::parser::at(section, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(section, "errors"); EXPECT_EQ(errors.size(), 0); } @@ -100,7 +103,7 @@ TEST(TestRulesetInfo, FailedWithErrorsRulesetInfo) EXPECT_STREQ(version.c_str(), "2.3.4"); auto section = ddwaf::parser::at(root_map, "rules"); - EXPECT_EQ(section.size(), 3); + EXPECT_EQ(section.size(), 4); auto loaded = ddwaf::parser::at(section, "loaded"); EXPECT_EQ(loaded.size(), 0); @@ -113,6 +116,9 @@ TEST(TestRulesetInfo, FailedWithErrorsRulesetInfo) EXPECT_NE(failed.find("fourth"), failed.end()); EXPECT_NE(failed.find("fifth"), failed.end()); + auto skipped = ddwaf::parser::at(section, "skipped"); + EXPECT_EQ(skipped.size(), 0); + auto errors = ddwaf::parser::at(section, "errors"); EXPECT_EQ(errors.size(), 3); { @@ -147,6 +153,47 @@ TEST(TestRulesetInfo, FailedWithErrorsRulesetInfo) ddwaf_object_free(&root); } +TEST(TestRulesetInfo, SkippedRulesetInfo) +{ + ddwaf::parameter root; + { + ruleset_info info; + info.set_ruleset_version("2.3.4"); + + auto §ion = info.add_section("rules"); + section.add_skipped("first"); + section.add_skipped("second"); + section.add_skipped("third"); + section.add_skipped("fourth"); + section.add_skipped("fifth"); + + info.to_object(root); + } + + auto root_map = static_cast(root); + EXPECT_EQ(root_map.size(), 2); + + auto version = ddwaf::parser::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "2.3.4"); + + auto section = ddwaf::parser::at(root_map, "rules"); + EXPECT_EQ(section.size(), 4); + + auto loaded = ddwaf::parser::at(section, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(section, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = ddwaf::parser::at(section, "skipped"); + EXPECT_EQ(skipped.size(), 5); + + auto errors = ddwaf::parser::at(section, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); +} + TEST(TestRulesetInfo, SectionErrorRulesetInfo) { ddwaf::parameter root; diff --git a/tests/semantic_version_test.cpp b/tests/semantic_version_test.cpp new file mode 100644 index 000000000..224d5c481 --- /dev/null +++ b/tests/semantic_version_test.cpp @@ -0,0 +1,173 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include +#include +#include + +#include "semver.hpp" + +#include "test_utils.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestVersion, Parsing) +{ + std::vector> samples{ + {"1.2.3", 1, 2, 3, 1002003}, + {"1.2.33", 1, 2, 33, 1002033}, + {"1.2.333", 1, 2, 333, 1002333}, + {"1.22.3", 1, 22, 3, 1022003}, + {"1.22.33", 1, 22, 33, 1022033}, + {"1.22.333", 1, 22, 333, 1022333}, + {"1.222.3", 1, 222, 3, 1222003}, + {"1.222.33", 1, 222, 33, 1222033}, + {"1.222.333", 1, 222, 333, 1222333}, + + {"11.2.3", 11, 2, 3, 11002003}, + {"11.2.33", 11, 2, 33, 11002033}, + {"11.2.333", 11, 2, 333, 11002333}, + {"11.22.3", 11, 22, 3, 11022003}, + {"11.22.33", 11, 22, 33, 11022033}, + {"11.22.333", 11, 22, 333, 11022333}, + {"11.222.3", 11, 222, 3, 11222003}, + {"11.222.33", 11, 222, 33, 11222033}, + {"11.222.333", 11, 222, 333, 11222333}, + + {"111.2.3", 111, 2, 3, 111002003}, + {"111.2.33", 111, 2, 33, 111002033}, + {"111.2.333", 111, 2, 333, 111002333}, + {"111.22.3", 111, 22, 3, 111022003}, + {"111.22.33", 111, 22, 33, 111022033}, + {"111.22.333", 111, 22, 333, 111022333}, + {"111.222.3", 111, 222, 3, 111222003}, + {"111.222.33", 111, 222, 33, 111222033}, + {"111.222.333", 111, 222, 333, 111222333}, + + {"1.2.3-alpha", 1, 2, 3, 1002003}, + {"111.222.333-beta", 111, 222, 333, 111222333}, + }; + + for (auto [str, major, minor, patch, number] : samples) { + semantic_version v{str}; + + EXPECT_EQ(v.major(), major); + EXPECT_EQ(v.minor(), minor); + EXPECT_EQ(v.patch(), patch); + + EXPECT_EQ(v.number(), number); + EXPECT_STREQ(v.cstring(), str.data()); + } +} + +TEST(TestVersion, LowerThan) +{ + std::vector samples{"1.2.3", "1.2.33", "1.2.333", "1.22.3", "1.22.33", + "1.22.333", "1.222.3", "1.222.33", "1.222.333", "11.2.3", "11.2.33", "11.2.333", "11.22.3", + "11.22.33", "11.22.333", "11.222.3", "11.222.33", "11.222.333", "111.2.3", "111.2.33", + "111.2.333", "111.22.3", "111.22.33", "111.22.333", "111.222.3", "111.222.33", + "111.222.333"}; + + for (std::size_t i = 0; i < samples.size(); ++i) { + auto lower = samples[i]; + for (std::size_t j = i + 1; j < samples.size(); ++j) { + auto higher = samples[j]; + EXPECT_LT(semantic_version{lower}, semantic_version{higher}); + } + } +} + +TEST(TestVersion, LowerEqual) +{ + std::vector samples{"1.2.3", "1.2.33", "1.2.333", "1.22.3", "1.22.33", + "1.22.333", "1.222.3", "1.222.33", "1.222.333", "11.2.3", "11.2.33", "11.2.333", "11.22.3", + "11.22.33", "11.22.333", "11.222.3", "11.222.33", "11.222.333", "111.2.3", "111.2.33", + "111.2.333", "111.22.3", "111.22.33", "111.22.333", "111.222.3", "111.222.33", + "111.222.333"}; + + for (std::size_t i = 0; i < samples.size(); ++i) { + auto lower = samples[i]; + for (std::size_t j = i; j < samples.size(); ++j) { + auto higher = samples[j]; + EXPECT_LE(semantic_version{lower}, semantic_version{higher}); + } + } +} + +TEST(TestVersion, GreaterThan) +{ + std::vector samples{"1.2.3", "1.2.33", "1.2.333", "1.22.3", "1.22.33", + "1.22.333", "1.222.3", "1.222.33", "1.222.333", "11.2.3", "11.2.33", "11.2.333", "11.22.3", + "11.22.33", "11.22.333", "11.222.3", "11.222.33", "11.222.333", "111.2.3", "111.2.33", + "111.2.333", "111.22.3", "111.22.33", "111.22.333", "111.222.3", "111.222.33", + "111.222.333"}; + + for (std::size_t i = 0; i < samples.size(); ++i) { + auto lower = samples[i]; + for (std::size_t j = i + 1; j < samples.size(); ++j) { + auto higher = samples[j]; + EXPECT_GT(semantic_version{higher}, semantic_version{lower}); + } + } +} + +TEST(TestVersion, GreaterEqual) +{ + std::vector samples{"1.2.3", "1.2.33", "1.2.333", "1.22.3", "1.22.33", + "1.22.333", "1.222.3", "1.222.33", "1.222.333", "11.2.3", "11.2.33", "11.2.333", "11.22.3", + "11.22.33", "11.22.333", "11.222.3", "11.222.33", "11.222.333", "111.2.3", "111.2.33", + "111.2.333", "111.22.3", "111.22.33", "111.22.333", "111.222.3", "111.222.33", + "111.222.333"}; + + for (std::size_t i = 0; i < samples.size(); ++i) { + auto lower = samples[i]; + for (std::size_t j = i; j < samples.size(); ++j) { + auto higher = samples[j]; + EXPECT_GE(semantic_version{higher}, semantic_version{lower}); + } + } +} + +TEST(TestVersion, Equality) +{ + std::vector samples{"1.2.3", "1.2.33", "1.2.333", "1.22.3", "1.22.33", + "1.22.333", "1.222.3", "1.222.33", "1.222.333", "11.2.3", "11.2.33", "11.2.333", "11.22.3", + "11.22.33", "11.22.333", "11.222.3", "11.222.33", "11.222.333", "111.2.3", "111.2.33", + "111.2.333", "111.22.3", "111.22.33", "111.22.333", "111.222.3", "111.222.33", + "111.222.333"}; + + for (auto str : samples) { EXPECT_EQ(semantic_version{str}, semantic_version{str}); } +} + +TEST(TestVersion, InvalidVersion) +{ + EXPECT_THROW(semantic_version{"a.b.c"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.b.c"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.2.c"}, std::invalid_argument); + + EXPECT_THROW(semantic_version{"1a.b.c"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.2b.c"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.2.3c"}, std::invalid_argument); + + EXPECT_THROW(semantic_version{"1"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.2"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.2.3.4"}, std::invalid_argument); +} + +TEST(TestVersion, OutOfRange) +{ + EXPECT_THROW(semantic_version{"1.2.3333"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.2222.3"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1111.2.3"}, std::invalid_argument); + + EXPECT_THROW(semantic_version{"1000.1.1"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.1000.1"}, std::invalid_argument); + EXPECT_THROW(semantic_version{"1.1.1000"}, std::invalid_argument); +} + +} // namespace diff --git a/tools/verify_ruleset.cpp b/tools/verify_ruleset.cpp index 48da0c89c..effb57675 100644 --- a/tools/verify_ruleset.cpp +++ b/tools/verify_ruleset.cpp @@ -18,7 +18,7 @@ int main(int argc, char *argv[]) int retval = EXIT_SUCCESS; try { - ddwaf_set_log_cb(log_cb, DDWAF_LOG_OFF); + ddwaf_set_log_cb(log_cb, DDWAF_LOG_TRACE); if (argc < 2) { std::cout << "Usage: " << argv[0] << " \n"; diff --git a/validator/runner.cpp b/validator/runner.cpp index 55f631b47..f9589253c 100644 --- a/validator/runner.cpp +++ b/validator/runner.cpp @@ -209,7 +209,14 @@ void test_runner::validate_conditions(const YAML::Node &expected, const YAML::No auto expected_cond = expected[i]; auto obtained_cond = obtained[i]; - expect(expected_cond["operator"], obtained_cond["operator"]); + // Remove the version of the operator as it is not reported within events + auto expected_operator = expected_cond["operator"].as(); + auto version_idx = expected_operator.find("@v"); + if (version_idx != std::string::npos) { + expected_operator = expected_operator.substr(0, version_idx); + } + + expect(expected_operator, obtained_cond["operator"].as()); auto op = expected_cond["operator"].as(); if (op == "match_regex") { diff --git a/validator/tests/rules/structured/ruleset.yaml b/validator/tests/rules/structured/ruleset.yaml index 603455af4..44ea77347 100644 --- a/validator/tests/rules/structured/ruleset.yaml +++ b/validator/tests/rules/structured/ruleset.yaml @@ -19,7 +19,7 @@ rules: - address: grpc.server.request.message - address: graphql.server.all_resolvers - address: graphql.server.resolver - operator: lfi_detector + operator: lfi_detector@v1 - id: rsp-930-002 name: SSRF Exploit detection tags: @@ -37,7 +37,7 @@ rules: - address: grpc.server.request.message - address: graphql.server.all_resolvers - address: graphql.server.resolver - operator: ssrf_detector + operator: ssrf_detector@v1 - id: rsp-930-003 name: SQLi Exploit detection tags: @@ -57,7 +57,7 @@ rules: - address: graphql.server.resolver db_type: - address: server.db.system - operator: sqli_detector + operator: sqli_detector@v2 - id: rsp-930-004 name: SHi Exploit detection tags: @@ -75,4 +75,4 @@ rules: - address: grpc.server.request.message - address: graphql.server.all_resolvers - address: graphql.server.resolver - operator: shi_detector + operator: shi_detector@v1