diff --git a/.gitignore b/.gitignore index 699ea3a7e..cb82b5acf 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ perf/test_files/breakdown.numbers .vscode *.swp *.swo +*.swn diff --git a/cmake/objects.cmake b/cmake/objects.cmake index 30674a7cf..7916ed66a 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -8,7 +8,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/event.cpp ${libddwaf_SOURCE_DIR}/src/object.cpp ${libddwaf_SOURCE_DIR}/src/object_store.cpp - ${libddwaf_SOURCE_DIR}/src/collection.cpp + ${libddwaf_SOURCE_DIR}/src/module.cpp ${libddwaf_SOURCE_DIR}/src/expression.cpp ${libddwaf_SOURCE_DIR}/src/ruleset_info.cpp ${libddwaf_SOURCE_DIR}/src/ip_utils.cpp @@ -22,6 +22,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/sha256.cpp ${libddwaf_SOURCE_DIR}/src/uuid.cpp ${libddwaf_SOURCE_DIR}/src/action_mapper.cpp + ${libddwaf_SOURCE_DIR}/src/builder/module_builder.cpp ${libddwaf_SOURCE_DIR}/src/builder/processor_builder.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/sql_base.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/pgsql.cpp diff --git a/src/builder/module_builder.cpp b/src/builder/module_builder.cpp new file mode 100644 index 000000000..53fc7e294 --- /dev/null +++ b/src/builder/module_builder.cpp @@ -0,0 +1,75 @@ +// 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 +#include +#include +#include + +#include "builder/module_builder.hpp" +#include "module.hpp" +#include "module_category.hpp" +#include "rule.hpp" + +namespace ddwaf { + +rule_module rule_module_builder::build() +{ + const auto &sort_fn = [this](const auto *left, const auto *right) { + const auto lverdict = left->get_verdict(); + const auto rverdict = right->get_verdict(); + const auto lsource = left->get_source(); + const auto rsource = right->get_source(); + return lverdict > rverdict || + (lverdict == rverdict && + (source_precedence_fn_(lsource, rsource) || + (lsource == rsource && grouping_key_fn_(left) < grouping_key_fn_(right)))); + }; + + // Sort first + std::sort(rules_.begin(), rules_.end(), sort_fn); + + // Generate collections by grouping based on the grouping key + std::string_view prev_key; + for (std::size_t i = 0; i < rules_.size(); ++i) { + const auto *rule = rules_[i]; + auto cur_key = grouping_key_fn_(rule); + if (cur_key != prev_key) { + if (!collections_.empty()) { + collections_.back().end = i; + } + using rule_collection = rule_module::rule_collection; + collections_.emplace_back(rule_collection{cur_key, rule->get_verdict(), i, i + 1}); + prev_key = cur_key; + } + } + + if (!collections_.empty()) { + collections_.back().end = rules_.size(); + } + + return rule_module{std::move(rules_), std::move(collections_), policy_}; +} + +std::array rule_module_set_builder::build( + const std::vector> &rules) +{ + std::array all_modules; + + for (const auto &rule : rules) { + auto &builder = builders_[static_cast(rule->get_module())]; + builder.insert(rule.get()); + } + + for (std::size_t i = 0; i < builders_.size(); ++i) { all_modules[i] = builders_[i].build(); } + + return all_modules; +} + +} // namespace ddwaf diff --git a/src/builder/module_builder.hpp b/src/builder/module_builder.hpp new file mode 100644 index 000000000..a93f2ef8f --- /dev/null +++ b/src/builder/module_builder.hpp @@ -0,0 +1,131 @@ +// 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 "module.hpp" +#include "module_category.hpp" +#include "rule.hpp" + +namespace ddwaf { + +// Helpers +inline bool user_rule_precedence( + const core_rule::source_type left, const core_rule::source_type right) +{ + return left > right; +} + +inline bool base_rule_precedence( + const core_rule::source_type left, const core_rule::source_type right) +{ + return left < right; +} + +[[gnu::const]] inline std::string_view type_grouping_key(const core_rule *rule) +{ + return rule->get_type(); +} +[[gnu::const]] constexpr std::string_view null_grouping_key(const core_rule * /*rule*/) +{ + return {}; +} + +// The module builder can be used to build a single module +class rule_module_builder { +public: + using source_type = core_rule::source_type; + using source_precedence_fn_type = bool (*)(source_type left, source_type right); + using grouping_key_fn_type = std::string_view (*)(const core_rule *rule); + using expiration_policy = rule_module::expiration_policy; + + rule_module_builder(source_precedence_fn_type source_precedence, + grouping_key_fn_type grouping_key, expiration_policy policy = expiration_policy::expiring) + : source_precedence_fn_(source_precedence), grouping_key_fn_(grouping_key), policy_(policy) + {} + ~rule_module_builder() = default; + rule_module_builder(rule_module_builder &&) = delete; + rule_module_builder(const rule_module_builder &) = delete; + rule_module_builder &operator=(rule_module_builder &&) = delete; + rule_module_builder &operator=(const rule_module_builder &) = delete; + + void insert(core_rule *rule) { rules_.emplace_back(rule); } + + rule_module build(); + +protected: + source_precedence_fn_type source_precedence_fn_; + grouping_key_fn_type grouping_key_fn_; + std::vector rules_; + std::vector collections_; + expiration_policy policy_; +}; + +class rule_module_set_builder { +public: + rule_module_set_builder() = default; + ~rule_module_set_builder() = default; + rule_module_set_builder(rule_module_set_builder &&) = delete; + rule_module_set_builder(const rule_module_set_builder &) = delete; + rule_module_set_builder &operator=(rule_module_set_builder &&) = delete; + rule_module_set_builder &operator=(const rule_module_set_builder &) = delete; + + std::array build( + const std::vector> &rules); + +protected: + /* Rules Ordering + * --------------------------- + * network_acl: (ignore timeout) + * - Collection: [order by: verdict (block then monitor), source (base then user)] + * + * authentication_acl: (ignore timeout) + * - Collection: [order by: verdict, source (base then user)] + * + * custom_acl: + * - Collection: [order by: verdict, source (user then base)] + * + * configuration: + * - Collection: [order by: verdict, source (user then base)] + * + * business_logic: + * - Collection: [order by: verdict, source (user then base)] + * + * rasp: + * - Collection: [order by: verdict, source (base then user)] + * + * waf: + * - Collection: [verdict=block, source=user, type=] + * - Collection: [verdict=block, source=user, type=...] + * - Collection: [verdict=block, source=base, type=] + * - Collection: [verdict=block, source=base, type=...] + * - Collection: [verdict=monitor, source=user, type=] + * - Collection: [verdict=monitor, source=user, type=...] + * - Collection: [verdict=monitor, source=base, type=] + * - Collection: [verdict=monitor, source=base, type=...] + * + * Note: Non expiring modules should always be placed before expiring modules. + */ + + std::array builders_{{ + // Network-ACL + {base_rule_precedence, null_grouping_key, rule_module::expiration_policy::non_expiring}, + // Authentication-ACL + {base_rule_precedence, null_grouping_key, rule_module::expiration_policy::non_expiring}, + // Custom-ACL + {user_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring}, + // Configuration + {user_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring}, + // Business logic + {user_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring}, + // RASP + {base_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring}, + // WAF + {user_rule_precedence, type_grouping_key, rule_module::expiration_policy::expiring}, + }}; +}; + +} // namespace ddwaf diff --git a/src/clock.hpp b/src/clock.hpp index 4fcaa21d1..05fbdc917 100644 --- a/src/clock.hpp +++ b/src/clock.hpp @@ -9,6 +9,12 @@ #include #include +#if defined __has_builtin +# if __has_builtin(__builtin_add_overflow) +# define HAS_BUILTIN_ADD_OVERFLOW +# endif +#endif + namespace ddwaf { #ifndef __linux__ using monotonic_clock = std::chrono::steady_clock; @@ -28,14 +34,14 @@ struct monotonic_clock { }; #endif // __linux__ -class timer { +// Syscall period refers to the number of calls to expired() before +// clock_gettime is called. This approach is only feasible because the +// WAF calls expired() quite often, otherwise another solution would be +// required to minimise syscalls. +template class base_timer { public: - // Syscall period refers to the number of calls to expired() before - // clock_gettime is called. This approach is only feasible because the - // WAF calls expired() quite often, otherwise another solution would be - // required to minimise syscalls. - explicit timer(std::chrono::microseconds exp, uint32_t syscall_period = default_syscall_period) - : start_(monotonic_clock::now()), end_(start_ + exp), syscall_period_(syscall_period) + explicit base_timer(std::chrono::nanoseconds exp) + : start_(monotonic_clock::now()), end_(add_saturated(start_, exp)) {} bool expired() @@ -44,7 +50,7 @@ class timer { if (end_ <= monotonic_clock::now()) { expired_ = true; } else { - calls_ = syscall_period_; + calls_ = SyscallPeriod; } } return expired_; @@ -58,12 +64,36 @@ class timer { } protected: - constexpr static uint32_t default_syscall_period{16}; + static monotonic_clock::time_point add_saturated( + monotonic_clock::time_point augend, std::chrono::nanoseconds addend) + { +#ifdef HAS_BUILTIN_ADD_OVERFLOW + using duration = monotonic_clock::duration; + + duration::rep augend_count = augend.time_since_epoch().count(); + duration::rep addend_count = addend.count(); + duration::rep result; + + if (__builtin_add_overflow(augend_count, addend_count, &result)) { + return monotonic_clock::time_point::max(); + } + + return monotonic_clock::time_point(duration(result)); +#else + return (addend > (monotonic_clock::time_point::max() - augend)) + ? monotonic_clock::time_point::max() + : augend + addend; +#endif + } monotonic_clock::time_point start_; monotonic_clock::time_point end_; - const uint32_t syscall_period_; uint32_t calls_{1}; bool expired_{false}; }; + +using timer = base_timer<16>; + +inline timer endless_timer() { return timer{std::chrono::nanoseconds::max()}; } + } // namespace ddwaf diff --git a/src/collection.cpp b/src/collection.cpp deleted file mode 100644 index 5cc3fc1e2..000000000 --- a/src/collection.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// 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 -#include -#include -#include - -#include "clock.hpp" -#include "collection.hpp" -#include "context_allocator.hpp" -#include "event.hpp" -#include "exception.hpp" -#include "exclusion/common.hpp" -#include "log.hpp" -#include "matcher/base.hpp" -#include "object_store.hpp" -#include "rule.hpp" - -namespace ddwaf { - -std::optional match_rule(rule *rule, const object_store &store, - memory::unordered_map &cache, - const exclusion::context_policy &policy, - const std::unordered_map> &dynamic_matchers, - ddwaf::timer &deadline) -{ - const auto &id = rule->get_id(); - - if (deadline.expired()) { - DDWAF_INFO("Ran out of time while evaluating rule '{}'", id); - throw timeout_exception(); - } - - if (!rule->is_enabled()) { - DDWAF_DEBUG("Rule '{}' is disabled", id); - return std::nullopt; - } - - std::string_view action_override; - auto exclusion = policy.find(rule); - if (exclusion.mode == exclusion::filter_mode::bypass) { - DDWAF_DEBUG("Bypassing rule '{}'", id); - return std::nullopt; - } - - if (exclusion.mode == exclusion::filter_mode::monitor) { - DDWAF_DEBUG("Monitoring rule '{}'", id); - action_override = "monitor"; - } else if (exclusion.mode == exclusion::filter_mode::custom) { - action_override = exclusion.action_override; - DDWAF_DEBUG("Evaluating rule '{}' with custom action '{}'", id, action_override); - } else { - DDWAF_DEBUG("Evaluating rule '{}'", id); - } - - try { - auto it = cache.find(rule); - if (it == cache.end()) { - auto [new_it, res] = cache.emplace(rule, rule::cache_type{}); - it = new_it; - } - - rule::cache_type &rule_cache = it->second; - std::optional event; - event = rule->match(store, rule_cache, exclusion.objects, dynamic_matchers, deadline); - - if (event.has_value()) { - event->action_override = action_override; - } - - return event; - } catch (const ddwaf::timeout_exception &) { - DDWAF_INFO("Ran out of time while evaluating rule '{}'", id); - throw; - } - - return std::nullopt; -} - -template -void base_collection::match(std::vector &events, object_store &store, - collection_cache &cache, const exclusion::context_policy &exclusion, - const std::unordered_map> &dynamic_matchers, - ddwaf::timer &deadline) const -{ - if (cache.result >= Derived::type()) { - // If the result was cached but ephemeral, clear it. Note that this is - // just a workaround taking advantage of the order of evaluation of - // collections. Collections might be removed in the future altogether. - if (cache.result == Derived::type() && cache.ephemeral) { - cache.result = collection_type::none; - cache.ephemeral = false; - } else { - return; - } - } - - for (auto *rule : rules_) { - auto event = - match_rule(rule, store, cache.rule_cache, exclusion, dynamic_matchers, deadline); - if (event.has_value()) { - cache.result = Derived::type(); - cache.ephemeral = event->ephemeral; - - events.emplace_back(std::move(*event)); - DDWAF_DEBUG("Found event on rule {}", rule->get_id()); - break; - } - } -} - -template class base_collection; -template class base_collection; - -} // namespace ddwaf diff --git a/src/collection.hpp b/src/collection.hpp deleted file mode 100644 index 6f4e883ce..000000000 --- a/src/collection.hpp +++ /dev/null @@ -1,66 +0,0 @@ -// 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 "context_allocator.hpp" -#include "event.hpp" -#include "exclusion/rule_filter.hpp" -#include "rule.hpp" - -namespace ddwaf { - -enum class collection_type : uint8_t { none = 0, regular = 1, priority = 2 }; - -// Collections are used to organize rules depending on their rule.type field. The overall goal -// behind the collection concept is to group rules of a similar nature and only evaluate as many of -// those rules until there is a match. Priority collections and regular collections only differ on -// how they interact with the cache, e.g. a priority collection will try to match even if there has -// already been a match in a regular collection. - -// The collection cache is shared by both priority and regular collections, this ensures that -// regular collections for which there is an equivalent priority collection of the same type, -// aren't processed when the respective priority collection has already had a match. -struct collection_cache { - bool ephemeral{false}; - collection_type result{collection_type::none}; - memory::unordered_map rule_cache; -}; - -template class base_collection { -public: - using object_set = std::unordered_set; - using cache_type = collection_cache; - - base_collection() = default; - ~base_collection() = default; - base_collection(const base_collection &) = default; - base_collection(base_collection &&) noexcept = default; - base_collection &operator=(const base_collection &) = default; - base_collection &operator=(base_collection &&) noexcept = default; - - void insert(const std::shared_ptr &rule) { rules_.emplace_back(rule.get()); } - - void match(std::vector &events, object_store &store, collection_cache &cache, - const exclusion::context_policy &exclusion, - const std::unordered_map> &dynamic_matchers, - ddwaf::timer &deadline) const; - -protected: - std::vector rules_{}; -}; - -class collection : public base_collection { -public: - static constexpr collection_type type() { return collection_type::regular; } -}; - -class priority_collection : public base_collection { -public: - static constexpr collection_type type() { return collection_type::priority; } -}; - -} // namespace ddwaf diff --git a/src/context.cpp b/src/context.cpp index f66c57bde..9dca5afc5 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -4,20 +4,23 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. #include +#include #include #include #include #include "clock.hpp" -#include "collection.hpp" #include "context.hpp" #include "ddwaf.h" #include "event.hpp" #include "exception.hpp" #include "exclusion/common.hpp" #include "log.hpp" +#include "module.hpp" #include "object_store.hpp" #include "processor/base.hpp" +#include "rule.hpp" +#include "target_address.hpp" #include "utils.hpp" namespace ddwaf { @@ -72,21 +75,9 @@ DDWAF_RET_CODE context::run(optional_ref persistent, return DDWAF_ERR_INVALID_OBJECT; } - // If the timeout provided is 0, we need to ensure the parameters are owned - // by the additive to ensure that the semantics of DDWAF_ERR_TIMEOUT are - // consistent across all possible timeout scenarios. - if (timeout == 0) { - if (res.has_value()) { - ddwaf_result &output = *res; - output.timeout = true; - } - return DDWAF_OK; - } - ddwaf::timer deadline{std::chrono::microseconds(timeout)}; - // If this is a new run but no rule care about those new params, let's skip the run - if (!is_first_run() && !store_.has_new_targets()) { + if (!store_.has_new_targets()) { return DDWAF_OK; } @@ -235,37 +226,14 @@ std::vector context::eval_rules( { std::vector events; - auto eval_collection = [&](const auto &type, const auto &collection) { - auto it = collection_cache_.find(type); - if (it == collection_cache_.end()) { - auto [new_it, res] = collection_cache_.emplace(type, collection_cache{}); - it = new_it; - } - collection.match(events, store_, it->second, policy, ruleset_->rule_matchers, deadline); - }; + for (std::size_t i = 0; i < ruleset_->rule_modules.size(); ++i) { + const auto &mod = ruleset_->rule_modules[i]; + auto &cache = rule_module_cache_[i]; - // Evaluate user priority collections first - for (auto &[type, collection] : ruleset_->user_priority_collections) { - DDWAF_DEBUG("Evaluating user priority collection '{}'", type); - eval_collection(type, collection); - } - - // Evaluate priority collections first - for (auto &[type, collection] : ruleset_->base_priority_collections) { - DDWAF_DEBUG("Evaluating priority collection '{}'", type); - eval_collection(type, collection); - } - - // Evaluate regular collection after - for (auto &[type, collection] : ruleset_->user_collections) { - DDWAF_DEBUG("Evaluating user collection '{}'", type); - eval_collection(type, collection); - } - - // Evaluate regular collection after - for (auto &[type, collection] : ruleset_->base_collections) { - DDWAF_DEBUG("Evaluating base collection '{}'", type); - eval_collection(type, collection); + auto verdict = mod.eval(events, store_, cache, policy, ruleset_->rule_matchers, deadline); + if (verdict == rule_module::verdict_type::block) { + break; + } } return events; diff --git a/src/context.hpp b/src/context.hpp index df6dcc2bf..4392da9ea 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -31,9 +31,13 @@ class context { explicit context(std::shared_ptr ruleset) : ruleset_(std::move(ruleset)) { + processor_cache_.reserve(ruleset_->preprocessors.size() + ruleset_->postprocessors.size()); rule_filter_cache_.reserve(ruleset_->rule_filters.size()); input_filter_cache_.reserve(ruleset_->input_filters.size()); - collection_cache_.reserve(ruleset_->collection_types.size()); + + for (std::size_t i = 0; i < ruleset_->rule_modules.size(); ++i) { + ruleset_->rule_modules[i].init_cache(rule_module_cache_[i]); + } } context(const context &) = delete; @@ -53,7 +57,7 @@ class context { std::vector eval_rules(const exclusion::context_policy &policy, ddwaf::timer &deadline); protected: - bool is_first_run() const { return collection_cache_.empty(); } + bool is_first_run() const { return store_.empty(); } bool check_new_rule_targets() const { // NOLINTNEXTLINE(readability-use-anyofallof) @@ -83,12 +87,12 @@ class context { memory::unordered_map processor_cache_; // Caches of filters and conditions - memory::unordered_map rule_filter_cache_{}; + memory::unordered_map rule_filter_cache_; memory::unordered_map input_filter_cache_; exclusion::context_policy exclusion_policy_; - // Cache of collections to avoid processing once a result has been obtained - memory::unordered_map collection_cache_{}; + // Cache of modules to avoid processing once a result has been obtained + std::array rule_module_cache_; }; class context_wrapper { diff --git a/src/event.cpp b/src/event.cpp index 2d6c64342..03d06fdcb 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -151,7 +151,7 @@ void add_action_to_tracker(action_tracker &actions, std::string_view id, action_ } } -void serialize_rule(const ddwaf::rule &rule, ddwaf_object &rule_map) +void serialize_rule(const core_rule &rule, ddwaf_object &rule_map) { ddwaf_object tmp; ddwaf_object tags_map; @@ -186,7 +186,7 @@ void serialize_empty_rule(ddwaf_object &rule_map) ddwaf_object_map_add(&rule_map, "tags", &tags_map); } -void serialize_and_consolidate_rule_actions(const ddwaf::rule &rule, ddwaf_object &rule_map, +void serialize_and_consolidate_rule_actions(const core_rule &rule, ddwaf_object &rule_map, std::string_view action_override, action_tracker &actions, ddwaf_object &stack_id) { const auto &rule_actions = rule.get_actions(); diff --git a/src/event.hpp b/src/event.hpp index 1d1d34445..325245ac0 100644 --- a/src/event.hpp +++ b/src/event.hpp @@ -15,10 +15,10 @@ namespace ddwaf { -class rule; +class core_rule; struct event { - const ddwaf::rule *rule{nullptr}; + const core_rule *rule{nullptr}; std::vector matches; bool ephemeral{false}; std::string_view action_override; diff --git a/src/exclusion/common.hpp b/src/exclusion/common.hpp index aa4437c52..05730a342 100644 --- a/src/exclusion/common.hpp +++ b/src/exclusion/common.hpp @@ -15,7 +15,7 @@ namespace ddwaf { -class rule; +class core_rule; namespace exclusion { @@ -69,19 +69,19 @@ struct rule_policy_ref { }; struct context_policy { - std::unordered_map persistent; - std::unordered_map ephemeral; + std::unordered_map persistent; + std::unordered_map ephemeral; [[nodiscard]] bool empty() const { return persistent.empty() && ephemeral.empty(); } [[nodiscard]] std::size_t size() const { return persistent.size() + ephemeral.size(); } - bool contains(const rule *key) const + bool contains(const core_rule *key) const { return persistent.contains(key) || ephemeral.contains(key); } - rule_policy_ref find(const rule *key) const + rule_policy_ref find(const core_rule *key) const { auto p_it = persistent.find(key); auto e_it = ephemeral.find(key); @@ -109,8 +109,8 @@ struct context_policy { {p_policy.objects, e_policy.objects}}; } - void add_rule_exclusion(const ddwaf::rule *rule, filter_mode mode, std::string_view action, - bool ephemeral_exclusion) + void add_rule_exclusion( + const core_rule *rule, filter_mode mode, std::string_view action, bool ephemeral_exclusion) { auto &rule_policy = ephemeral_exclusion ? ephemeral : persistent; @@ -122,7 +122,7 @@ struct context_policy { } } - void add_input_exclusion(const ddwaf::rule *rule, const object_set &objects) + void add_input_exclusion(const core_rule *rule, const object_set &objects) { if (!objects.persistent.empty()) { auto &rule_policy = persistent[rule]; diff --git a/src/exclusion/input_filter.cpp b/src/exclusion/input_filter.cpp index dfcdc7330..2e3305dae 100644 --- a/src/exclusion/input_filter.cpp +++ b/src/exclusion/input_filter.cpp @@ -26,7 +26,7 @@ namespace ddwaf::exclusion { using excluded_set = input_filter::excluded_set; input_filter::input_filter(std::string id, std::shared_ptr expr, - std::set rule_targets, std::shared_ptr filter) + std::set rule_targets, std::shared_ptr filter) : id_(std::move(id)), expr_(std::move(expr)), rule_targets_(std::move(rule_targets)), filter_(std::move(filter)) { diff --git a/src/exclusion/input_filter.hpp b/src/exclusion/input_filter.hpp index b4869fe9e..3d604ee6d 100644 --- a/src/exclusion/input_filter.hpp +++ b/src/exclusion/input_filter.hpp @@ -20,7 +20,7 @@ namespace ddwaf::exclusion { class input_filter { public: struct excluded_set { - const std::set &rules; + const std::set &rules; object_set objects; }; @@ -29,8 +29,8 @@ class input_filter { object_filter::cache_type object_filter_cache; }; - input_filter(std::string id, std::shared_ptr expr, std::set rule_targets, - std::shared_ptr filter); + input_filter(std::string id, std::shared_ptr expr, + std::set rule_targets, std::shared_ptr filter); input_filter(const input_filter &) = delete; input_filter &operator=(const input_filter &) = delete; input_filter(input_filter &&) = default; @@ -52,7 +52,7 @@ class input_filter { protected: std::string id_; std::shared_ptr expr_; - const std::set rule_targets_; + const std::set rule_targets_; std::shared_ptr filter_; }; diff --git a/src/exclusion/rule_filter.cpp b/src/exclusion/rule_filter.cpp index b117a1a83..0c1dae165 100644 --- a/src/exclusion/rule_filter.cpp +++ b/src/exclusion/rule_filter.cpp @@ -25,7 +25,7 @@ namespace ddwaf::exclusion { using excluded_set = rule_filter::excluded_set; rule_filter::rule_filter(std::string id, std::shared_ptr expr, - std::set rule_targets, filter_mode mode, std::string action) + std::set rule_targets, filter_mode mode, std::string action) : id_(std::move(id)), expr_(std::move(expr)), mode_(mode), action_(std::move(action)) { if (!expr_) { diff --git a/src/exclusion/rule_filter.hpp b/src/exclusion/rule_filter.hpp index db9be61b7..12952ae7d 100644 --- a/src/exclusion/rule_filter.hpp +++ b/src/exclusion/rule_filter.hpp @@ -20,7 +20,7 @@ namespace ddwaf::exclusion { class rule_filter { public: struct excluded_set { - const std::unordered_set &rules; + const std::unordered_set &rules; bool ephemeral{false}; filter_mode mode{filter_mode::none}; std::string_view action; @@ -28,8 +28,9 @@ class rule_filter { using cache_type = expression::cache_type; - rule_filter(std::string id, std::shared_ptr expr, std::set rule_targets, - filter_mode mode = filter_mode::bypass, std::string action = {}); + rule_filter(std::string id, std::shared_ptr expr, + std::set rule_targets, filter_mode mode = filter_mode::bypass, + std::string action = {}); rule_filter(const rule_filter &) = delete; rule_filter &operator=(const rule_filter &) = delete; rule_filter(rule_filter &&) = default; @@ -52,9 +53,9 @@ class rule_filter { protected: std::string id_; std::shared_ptr expr_; - std::unordered_set rule_targets_; + std::unordered_set rule_targets_; filter_mode mode_; - std::string action_{}; + std::string action_; }; } // namespace ddwaf::exclusion diff --git a/src/interface.cpp b/src/interface.cpp index 90467821e..da6bab70c 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -4,6 +4,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include +#include #include #include #include @@ -230,6 +232,11 @@ DDWAF_RET_CODE ddwaf_run(ddwaf_context context, ddwaf_object *persistent_data, ephemeral = *ephemeral_data; } + // The timers will actually count nanoseconds, std::chrono doesn't + // deal well with durations being beyond range. + constexpr uint64_t max_timeout_ms = std::chrono::nanoseconds::max().count() / 1000; + timeout = std::min(timeout, max_timeout_ms); + return context->run(persistent, ephemeral, res, timeout); } catch (const std::exception &e) { // catch-all to avoid std::terminate diff --git a/src/module.cpp b/src/module.cpp new file mode 100644 index 000000000..ded4e3fca --- /dev/null +++ b/src/module.cpp @@ -0,0 +1,167 @@ +// 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 +#include +#include +#include +#include +#include + +#include "clock.hpp" +#include "event.hpp" +#include "exception.hpp" +#include "exclusion/common.hpp" +#include "log.hpp" +#include "matcher/base.hpp" +#include "object_store.hpp" +#include "rule.hpp" + +namespace ddwaf { + +namespace { +using verdict_type = rule_module::verdict_type; + +std::pair, verdict_type> eval_rule(const core_rule &rule, + const object_store &store, core_rule::cache_type &cache, + const exclusion::context_policy &policy, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) +{ + const auto &id = rule.get_id(); + auto verdict = rule.get_verdict(); + + if (deadline.expired()) { + DDWAF_INFO("Ran out of time while evaluating rule '{}'", id); + throw timeout_exception(); + } + + if (!rule.is_enabled()) { + DDWAF_DEBUG("Rule '{}' is disabled", id); + return {std::nullopt, verdict_type::none}; + } + + std::string_view action_override; + auto exclusion = policy.find(&rule); + if (exclusion.mode == exclusion::filter_mode::bypass) { + DDWAF_DEBUG("Bypassing rule '{}'", id); + return {std::nullopt, verdict_type::none}; + } + + if (exclusion.mode == exclusion::filter_mode::monitor) { + action_override = "monitor"; + verdict = verdict_type::monitor; + DDWAF_DEBUG("Monitoring rule '{}'", id); + } else if (exclusion.mode == exclusion::filter_mode::custom) { + action_override = exclusion.action_override; + verdict = verdict_type::block; + DDWAF_DEBUG("Evaluating rule '{}' with custom action '{}'", id, action_override); + } else { + DDWAF_DEBUG("Evaluating rule '{}'", id); + } + + try { + std::optional event; + event = rule.match(store, cache, exclusion.objects, dynamic_matchers, deadline); + + if (event.has_value()) { + event->action_override = action_override; + } + + return {event, verdict}; + } catch (const ddwaf::timeout_exception &) { + DDWAF_INFO("Ran out of time while evaluating rule '{}'", id); + throw; + } + + return {std::nullopt, verdict_type::none}; +} + +} // namespace + +ddwaf::timer &rule_module::get_deadline(ddwaf::timer &deadline) const +{ + static auto no_deadline = endless_timer(); + return may_expire() ? deadline : no_deadline; +} + +verdict_type rule_module::eval_with_collections(std::vector &events, object_store &store, + cache_type &cache, const exclusion::context_policy &exclusion, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const +{ + verdict_type final_verdict = verdict_type::none; + for (const auto &collection : collections_) { + DDWAF_DEBUG("Evaluating collection: {}", collection.name); + auto &collection_cache = cache.collections[collection.name]; + if (collection_cache.type >= collection.type) { + // If the result was cached but ephemeral, clear it. Note that this is + // just a workaround taking advantage of the order of evaluation of + // collections. Collections might be removed in the future altogether. + if (collection_cache.type == collection.type && collection_cache.ephemeral) { + collection_cache.type = core_rule::verdict_type::none; + collection_cache.ephemeral = false; + } else { + continue; + } + } + + for (std::size_t i = collection.begin; i < collection.end; ++i) { + auto &rule = *rules_[i]; + auto [event, verdict] = + eval_rule(rule, store, cache.rules[i], exclusion, dynamic_matchers, deadline); + if (event.has_value()) { + collection_cache.type = verdict; + collection_cache.ephemeral = event->ephemeral; + + events.emplace_back(std::move(*event)); + DDWAF_DEBUG("Found event on rule {}", rule.get_id()); + + if (verdict == verdict_type::block) { + return verdict_type::block; + } + + final_verdict = verdict_type::monitor; + break; + } + } + } + return final_verdict; +} + +verdict_type rule_module::eval(std::vector &events, object_store &store, cache_type &cache, + const exclusion::context_policy &exclusion, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const +{ + auto &apt_deadline = get_deadline(deadline); + + if (collections_.empty()) { + auto final_verdict = verdict_type::none; + for (std::size_t i = 0; i < rules_.size(); ++i) { + const auto &rule = *rules_[i]; + auto &rule_cache = cache.rules[i]; + + auto [event, verdict] = + eval_rule(rule, store, rule_cache, exclusion, dynamic_matchers, apt_deadline); + if (event.has_value()) { + events.emplace_back(std::move(*event)); + DDWAF_DEBUG("Found event on rule {}", rule.get_id()); + final_verdict = verdict; + if (final_verdict == verdict_type::block) { + break; + } + } + } + return final_verdict; + } + + return eval_with_collections(events, store, cache, exclusion, dynamic_matchers, apt_deadline); +} +} // namespace ddwaf diff --git a/src/module.hpp b/src/module.hpp new file mode 100644 index 000000000..4cf22b5d0 --- /dev/null +++ b/src/module.hpp @@ -0,0 +1,87 @@ +// 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 "context_allocator.hpp" +#include "event.hpp" +#include "rule.hpp" + +#include + +namespace ddwaf { + +struct rule_collection_cache { + core_rule::verdict_type type{core_rule::verdict_type::none}; + bool ephemeral{false}; +}; + +struct rule_module_cache { + memory::vector rules; + memory::unordered_map collections; +}; + +class rule_module { +public: + using verdict_type = core_rule::verdict_type; + using cache_type = rule_module_cache; + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + enum class expiration_policy : uint8_t { + expiring, + non_expiring, + }; + + rule_module() = default; + ~rule_module() = default; + rule_module(const rule_module &) = default; + rule_module(rule_module &&) noexcept = default; + rule_module &operator=(const rule_module &) = default; + rule_module &operator=(rule_module &&) noexcept = default; + + void init_cache(cache_type &cache) const + { + cache.rules.resize(rules_.size()); + cache.collections.reserve(collections_.size()); + } + + [[nodiscard]] bool may_expire() const { return policy_ == expiration_policy::expiring; } + + verdict_type eval(std::vector &events, object_store &store, cache_type &cache, + const exclusion::context_policy &exclusion, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const; + +protected: + verdict_type eval_with_collections(std::vector &events, object_store &store, + cache_type &cache, const exclusion::context_policy &exclusion, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const; + + ddwaf::timer &get_deadline(ddwaf::timer &deadline) const; + + struct rule_collection { + std::string_view name; + verdict_type type; + std::size_t begin; + std::size_t end; + }; + + explicit rule_module(std::vector &&rules, + std::vector &&collections, + expiration_policy policy = expiration_policy::expiring) + : rules_(std::move(rules)), collections_(std::move(collections)), policy_(policy) + {} + + std::vector rules_; + std::vector collections_; + expiration_policy policy_{expiration_policy::expiring}; + + friend class rule_module_builder; +}; + +} // namespace ddwaf diff --git a/src/module_category.hpp b/src/module_category.hpp new file mode 100644 index 000000000..d75d1cf01 --- /dev/null +++ b/src/module_category.hpp @@ -0,0 +1,48 @@ +// 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 + +namespace ddwaf { +enum class rule_module_category : uint8_t { + network_acl = 0, + authentication_acl, + custom_acl, + configuration, + business_logic, + rasp, + waf, +}; + +constexpr std::size_t rule_module_count = static_cast(rule_module_category::waf) + 1; + +inline rule_module_category string_to_rule_module_category(std::string_view name) +{ + if (name == "network-acl") { + return rule_module_category::network_acl; + } + if (name == "authentication-acl") { + return rule_module_category::authentication_acl; + } + if (name == "custom-acl") { + return rule_module_category::custom_acl; + } + if (name == "configuration") { + return rule_module_category::configuration; + } + if (name == "business-logic") { + return rule_module_category::business_logic; + } + if (name == "rasp") { + return rule_module_category::rasp; + } + return rule_module_category::waf; +} + +} // namespace ddwaf diff --git a/src/object_store.cpp b/src/object_store.cpp index 381237c20..af759f50d 100644 --- a/src/object_store.cpp +++ b/src/object_store.cpp @@ -10,7 +10,7 @@ #include "ddwaf.h" #include "log.hpp" #include "object_store.hpp" -#include "utils.hpp" +#include "target_address.hpp" namespace ddwaf { diff --git a/src/object_store.hpp b/src/object_store.hpp index e783d0ad4..de80f79d2 100644 --- a/src/object_store.hpp +++ b/src/object_store.hpp @@ -13,6 +13,7 @@ #include "context_allocator.hpp" #include "ddwaf.h" +#include "target_address.hpp" #include "utils.hpp" namespace ddwaf { diff --git a/src/parameter.hpp b/src/parameter.hpp index 0fb9fb3c7..cd02bb1a1 100644 --- a/src/parameter.hpp +++ b/src/parameter.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -25,7 +26,7 @@ class parameter : public ddwaf_object { using string_set = std::unordered_set; parameter() = default; - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) parameter(const ddwaf_object &arg) : _ddwaf_object() { *((ddwaf_object *)this) = arg; } parameter(const parameter &) = default; diff --git a/src/parser/actions_parser.cpp b/src/parser/actions_parser.cpp index 9ef773d7c..ad111bc89 100644 --- a/src/parser/actions_parser.cpp +++ b/src/parser/actions_parser.cpp @@ -36,6 +36,7 @@ void validate_and_add_redirect( { auto it = parameters.find("location"); if (it == parameters.end() || it->second.empty()) { + // TODO add a log message builder.alias_default_action_to("block", id); return; } diff --git a/src/parser/exclusion_parser.cpp b/src/parser/exclusion_parser.cpp index 7b6db9e2e..c634a2858 100644 --- a/src/parser/exclusion_parser.cpp +++ b/src/parser/exclusion_parser.cpp @@ -21,6 +21,7 @@ #include "parser/parser.hpp" #include "parser/specification.hpp" #include "semver.hpp" +#include "target_address.hpp" #include "utils.hpp" #include "version.hpp" diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index 7f332f05c..c3fe1cd9c 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -34,6 +34,7 @@ #include "parser/common.hpp" #include "parser/matcher_parser.hpp" #include "parser/parser.hpp" +#include "target_address.hpp" #include "transformer/base.hpp" #include "utils.hpp" diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 3e3bd5a08..ac251362e 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -32,7 +32,7 @@ namespace v2 { rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info &info, std::unordered_map &rule_data_ids, const object_limits &limits, - rule::source_type source = rule::source_type::base); + core_rule::source_type source = core_rule::source_type::base); matcher_container parse_data(parameter::vector &data_array, std::unordered_map &data_ids_to_type, base_section_info &info); diff --git a/src/parser/parser_v1.cpp b/src/parser/parser_v1.cpp index edb141d32..e4c96938a 100644 --- a/src/parser/parser_v1.cpp +++ b/src/parser/parser_v1.cpp @@ -32,6 +32,7 @@ #include "rule.hpp" #include "ruleset.hpp" #include "ruleset_info.hpp" +#include "target_address.hpp" #include "transformer/base.hpp" #include "utils.hpp" @@ -122,8 +123,9 @@ std::shared_ptr parse_expression(parameter::vector &conditions_array return std::make_shared(std::move(conditions)); } -void parseRule(parameter::map &rule, base_section_info &info, - std::unordered_set &rule_ids, ddwaf::ruleset &rs, ddwaf::object_limits limits) +void parse_rule(parameter::map &rule, base_section_info &info, + std::unordered_set &rule_ids, std::vector> &rules, + ddwaf::object_limits limits) { auto id = at(rule, "id"); if (rule_ids.find(id) != rule_ids.end()) { @@ -164,11 +166,11 @@ void parseRule(parameter::map &rule, base_section_info &info, throw ddwaf::parsing_error("missing key 'type'"); } - auto rule_ptr = std::make_shared( + auto rule_ptr = std::make_shared( std::string(id), at(rule, "name"), std::move(tags), std::move(expression)); rule_ids.emplace(rule_ptr->get_id()); - rs.insert_rule(rule_ptr); + rules.emplace_back(rule_ptr); info.add_loaded(rule_ptr->get_id()); } catch (const std::exception &e) { DDWAF_WARN("failed to parse rule '{}': {}", id, e.what()); @@ -187,11 +189,13 @@ void parse( auto §ion = info.add_section("rules"); std::unordered_set rule_ids; + std::vector> rules; for (unsigned i = 0; i < rules_array.size(); ++i) { const auto &rule_param = rules_array[i]; try { auto rule = static_cast(rule_param); - parseRule(rule, section, rule_ids, rs, limits); + parse_rule(rule, section, rule_ids, rules, limits); + rs.insert_rules(rules, {}); } catch (const std::exception &e) { DDWAF_WARN("{}", e.what()); section.add_failed("index:" + to_string(i), e.what()); diff --git a/src/parser/processor_parser.cpp b/src/parser/processor_parser.cpp index a720624a8..d76688119 100644 --- a/src/parser/processor_parser.cpp +++ b/src/parser/processor_parser.cpp @@ -21,6 +21,7 @@ #include "processor/extract_schema.hpp" #include "processor/fingerprint.hpp" #include "semver.hpp" +#include "target_address.hpp" #include "utils.hpp" #include "version.hpp" diff --git a/src/parser/rule_parser.cpp b/src/parser/rule_parser.cpp index 3d2eadbe7..44ad6120f 100644 --- a/src/parser/rule_parser.cpp +++ b/src/parser/rule_parser.cpp @@ -28,7 +28,7 @@ namespace { rule_spec parse_rule(parameter::map &rule, std::unordered_map &rule_data_ids, const object_limits &limits, - rule::source_type source, address_container &addresses) + core_rule::source_type source, address_container &addresses) { std::vector rule_transformers; auto data_source = ddwaf::data_source::values; @@ -68,7 +68,7 @@ rule_spec parse_rule(parameter::map &rule, rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info &info, std::unordered_map &rule_data_ids, const object_limits &limits, - rule::source_type source) + core_rule::source_type source) { rule_spec_container rules; for (unsigned i = 0; i < rule_array.size(); ++i) { diff --git a/src/parser/specification.hpp b/src/parser/specification.hpp index 750ba2c1e..d741b4fc5 100644 --- a/src/parser/specification.hpp +++ b/src/parser/specification.hpp @@ -21,7 +21,7 @@ namespace ddwaf::parser { struct rule_spec { bool enabled; - rule::source_type source; + core_rule::source_type source; std::string name; std::unordered_map tags; std::shared_ptr expr; diff --git a/src/processor/fingerprint.cpp b/src/processor/fingerprint.cpp index c6c8d9480..2a5d6dd60 100644 --- a/src/processor/fingerprint.cpp +++ b/src/processor/fingerprint.cpp @@ -586,6 +586,10 @@ std::pair http_header_fingerprint::eval_i const unary_argument &headers, processor_cache & /*cache*/, ddwaf::timer &deadline) const { + if (headers.value->type != DDWAF_OBJ_MAP) { + return {{}, object_store::attribute::none}; + } + std::string known_header_bitset; known_header_bitset.resize(standard_headers_length, '0'); @@ -632,6 +636,10 @@ std::pair http_network_fingerprint::eval_ const unary_argument &headers, processor_cache & /*cache*/, ddwaf::timer &deadline) const { + if (headers.value->type != DDWAF_OBJ_MAP) { + return {{}, object_store::attribute::none}; + } + std::string ip_origin_bitset; ip_origin_bitset.resize(ip_origin_headers_length, '0'); diff --git a/src/rule.hpp b/src/rule.hpp index a449e56f1..af28bc288 100644 --- a/src/rule.hpp +++ b/src/rule.hpp @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include @@ -16,36 +15,48 @@ #include "event.hpp" #include "exclusion/common.hpp" #include "expression.hpp" -#include "iterator.hpp" #include "matcher/base.hpp" +#include "module_category.hpp" #include "object_store.hpp" namespace ddwaf { -class rule { +// A core rule constitutes the most important type of entity within the +// evaluation process. These rules are "request-bound", i.e. they are used to +// specifically analyse request data, as opposed to other types of rules such +// as threshold rules which analyse data across requests. +class core_rule { public: enum class source_type : uint8_t { base = 1, user = 2 }; + enum class verdict_type : uint8_t { none = 0, monitor = 1, block = 2 }; using cache_type = expression::cache_type; - rule(std::string id, std::string name, std::unordered_map tags, + core_rule(std::string id, std::string name, std::unordered_map tags, std::shared_ptr expr, std::vector actions = {}, - bool enabled = true, source_type source = source_type::base) - : enabled_(enabled), source_(source), id_(std::move(id)), name_(std::move(name)), - tags_(std::move(tags)), expr_(std::move(expr)), actions_(std::move(actions)) + bool enabled = true, source_type source = source_type::base, + verdict_type verdict = verdict_type::monitor) + : enabled_(enabled), source_(source), verdict_(verdict), id_(std::move(id)), + name_(std::move(name)), tags_(std::move(tags)), actions_(std::move(actions)), + expr_(std::move(expr)) { if (!expr_) { throw std::invalid_argument("rule constructed with null expression"); } + + // If the tag is not present, the default is `waf` + mod_ = string_to_rule_module_category(get_tag_or("module", "waf")); + // Type is guaranteed to be present + type_ = get_tag("type"); } - rule(const rule &) = delete; - rule &operator=(const rule &) = delete; + core_rule(const core_rule &) = delete; + core_rule &operator=(const core_rule &) = delete; - rule(rule &&rhs) noexcept = default; - rule &operator=(rule &&rhs) = default; + core_rule(core_rule &&rhs) noexcept = default; + core_rule &operator=(core_rule &&rhs) = default; - virtual ~rule() = default; + virtual ~core_rule() = default; virtual std::optional match(const object_store &store, cache_type &cache, const exclusion::object_set_ref &objects_excluded, @@ -68,9 +79,12 @@ class rule { [[nodiscard]] bool is_enabled() const { return enabled_; } void toggle(bool value) { enabled_ = value; } - source_type get_source() const { return source_; } - const std::string &get_id() const { return id_; } - const std::string &get_name() const { return name_; } + [[nodiscard]] source_type get_source() const { return source_; } + + std::string_view get_id() const { return id_; } + std::string_view get_name() const { return name_; } + std::string_view get_type() const { return type_; } + rule_module_category get_module() const { return mod_; } std::string_view get_tag(const std::string &tag) const { @@ -98,24 +112,36 @@ class rule { } } + [[nodiscard]] bool has_actions() const { return !actions_.empty(); } const std::vector &get_actions() const { return actions_; } + void set_actions(std::vector new_actions) { actions_ = std::move(new_actions); } + void set_verdict(verdict_type verdict) { verdict_ = verdict; } + verdict_type get_verdict() const { return verdict_; } void get_addresses(std::unordered_map &addresses) const { return expr_->get_addresses(addresses); } - void set_actions(std::vector new_actions) { actions_ = std::move(new_actions); } - protected: + // General metadata bool enabled_{true}; source_type source_; + verdict_type verdict_{verdict_type::monitor}; std::string id_; std::string name_; std::unordered_map tags_; + std::vector actions_; + + // Frequently accessed tags + std::string_view type_; + rule_module_category mod_; + + // Tags provided through rules_override std::unordered_map ancillary_tags_; + + // Evaluable expression encompassing all the rule's conditions std::shared_ptr expr_; - std::vector actions_; }; } // namespace ddwaf diff --git a/src/ruleset.hpp b/src/ruleset.hpp index 19e9edd0e..b5dda5253 100644 --- a/src/ruleset.hpp +++ b/src/ruleset.hpp @@ -11,9 +11,10 @@ #include #include "action_mapper.hpp" -#include "collection.hpp" +#include "builder/module_builder.hpp" #include "exclusion/input_filter.hpp" #include "exclusion/rule_filter.hpp" +#include "module.hpp" #include "obfuscator.hpp" #include "processor/base.hpp" #include "rule.hpp" @@ -22,33 +23,22 @@ namespace ddwaf { struct ruleset { - void insert_rule(const std::shared_ptr &rule) + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + void insert_rules(const std::vector> &base, + const std::vector> &user) { - rules.emplace_back(rule); - std::string_view type = rule->get_tag("type"); - std::string_view mod = rule->get_tag_or("module", "waf"); - - auto [it, res] = collection_types.emplace(ddwaf::fmt::format("{}.{}", mod, type)); - const auto &collection = *it; - if (rule->get_actions().empty()) { - if (rule->get_source() == rule::source_type::user) { - user_collections[collection].insert(rule); - } else { - base_collections[collection].insert(rule); - } - } else { - if (rule->get_source() == rule::source_type::user) { - user_priority_collections[collection].insert(rule); - } else { - base_priority_collections[collection].insert(rule); - } + for (const auto &rule : base) { + rule->get_addresses(rule_addresses); + rules.emplace_back(rule); + } + for (const auto &rule : user) { + rule->get_addresses(rule_addresses); + rules.emplace_back(rule); } - rule->get_addresses(rule_addresses); - } - void insert_rules(const std::vector> &rules_) - { - for (const auto &rule : rules_) { insert_rule(rule); } + // TODO this could be done with rules vector + rule_module_set_builder builder; + rule_modules = builder.build(rules); } template @@ -164,19 +154,15 @@ struct ruleset { std::unordered_map> rule_filters; std::unordered_map> input_filters; - std::vector> rules; + std::vector> rules; std::unordered_map> rule_matchers; std::unordered_map> exclusion_matchers; std::vector> scanners; std::shared_ptr actions; - // The key used to organise collections is "${rule.module}.${rule.type}" - std::unordered_set collection_types; - std::unordered_map user_priority_collections; - std::unordered_map base_priority_collections; - std::unordered_map user_collections; - std::unordered_map base_collections; + // Rule modules + std::array rule_modules; std::unordered_map rule_addresses; std::unordered_map filter_addresses; diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp index 4ebe3e1a8..c2239a9a8 100644 --- a/src/ruleset_builder.cpp +++ b/src/ruleset_builder.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -47,10 +48,10 @@ constexpr ruleset_builder::change_state operator&( namespace { -std::set references_to_rules( - const std::vector &references, const indexer &rules) +std::set references_to_rules( + const std::vector &references, const indexer &rules) { - std::set rule_refs; + std::set rule_refs; if (!references.empty()) { for (const auto &ref : references) { if (ref.type == parser::reference_type::id) { @@ -71,6 +72,23 @@ std::set references_to_rules( return rule_refs; } +core_rule::verdict_type obtain_rule_verdict( + const action_mapper &mapper, const std::vector &rule_actions) +{ + for (const auto &action : rule_actions) { + auto it = mapper.find(action); + if (it == mapper.end()) { + continue; + } + + auto action_mode = it->second.type; + if (is_blocking_action(action_mode)) { + return core_rule::verdict_type::block; + } + } + return core_rule::verdict_type::monitor; +} + } // namespace std::shared_ptr ruleset_builder::build(parameter::map &root, base_ruleset_info &info) @@ -82,9 +100,12 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules return {}; } - constexpr static change_state base_rule_update = change_state::rules | change_state::overrides; + constexpr static change_state base_rule_update = + change_state::rules | change_state::overrides | change_state::actions; + constexpr static change_state custom_rule_update = + change_state::custom_rules | change_state::actions; constexpr static change_state filters_update = - base_rule_update | change_state::custom_rules | change_state::filters; + base_rule_update | custom_rule_update | change_state::filters; constexpr static change_state processors_update = change_state::processors | change_state::scanners; // When a configuration with 'rules' or 'rules_override' is received, we @@ -95,7 +116,7 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules // Initially, new rules are generated from their spec for (const auto &[id, spec] : base_rules_) { - auto rule_ptr = std::make_shared( + auto rule_ptr = std::make_shared( id, spec.name, spec.tags, spec.expr, spec.actions, spec.enabled, spec.source); final_base_rules_.emplace(rule_ptr); } @@ -135,23 +156,27 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules } } - // Remove any disabled rules + // Update blocking mode and remove any disabled rules for (auto it = final_base_rules_.begin(); it != final_base_rules_.end();) { if (!(*it)->is_enabled()) { it = final_base_rules_.erase(it); - } else { - ++it; + continue; } + + auto mode = obtain_rule_verdict(*actions_, (*it)->get_actions()); + (*it)->set_verdict(mode); + + ++it; } } - if ((state & change_state::custom_rules) != change_state::none) { + if ((state & custom_rule_update) != change_state::none) { final_user_rules_.clear(); - // Initially, new rules are generated from their spec for (const auto &[id, spec] : user_rules_) { - auto rule_ptr = std::make_shared( - id, spec.name, spec.tags, spec.expr, spec.actions, spec.enabled, spec.source); + auto mode = obtain_rule_verdict(*actions_, spec.actions); + auto rule_ptr = std::make_shared( + id, spec.name, spec.tags, spec.expr, spec.actions, spec.enabled, spec.source, mode); if (!rule_ptr->is_enabled()) { // Skip disabled rules continue; @@ -202,9 +227,8 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules } } - auto rs = std::make_shared(); - rs->insert_rules(final_base_rules_.items()); - rs->insert_rules(final_user_rules_.items()); + auto rs = std::make_shared(); + rs->insert_rules(final_base_rules_.items(), final_user_rules_.items()); rs->insert_filters(rule_filters_); rs->insert_filters(input_filters_); rs->insert_preprocessors(preprocessors_); @@ -220,7 +244,7 @@ std::shared_ptr ruleset_builder::build(parameter::map &root, base_rules // again that there are rules available. if (rs->rules.empty()) { DDWAF_WARN("No valid rules found"); - throw ddwaf::parsing_error("no valid or enabled rules found"); + throw parsing_error("no valid or enabled rules found"); } return rs; @@ -292,7 +316,7 @@ ruleset_builder::change_state ruleset_builder::load(parameter::map &root, base_r decltype(rule_data_ids_) rule_data_ids; auto new_user_rules = parser::v2::parse_rules( - rules, section, rule_data_ids, limits_, rule::source_type::user); + rules, section, rule_data_ids, limits_, core_rule::source_type::user); user_rules_ = std::move(new_user_rules); } else { DDWAF_DEBUG("Clearing all custom rules"); @@ -309,7 +333,7 @@ ruleset_builder::change_state ruleset_builder::load(parameter::map &root, base_r // If we haven't received rules and our base ruleset is empty, the // WAF can't proceed. DDWAF_WARN("No valid rules found"); - throw ddwaf::parsing_error("no valid rules found"); + throw parsing_error("no valid rules found"); } it = root.find("rules_data"); diff --git a/src/ruleset_builder.hpp b/src/ruleset_builder.hpp index 453e752f5..4d163492f 100644 --- a/src/ruleset_builder.hpp +++ b/src/ruleset_builder.hpp @@ -96,8 +96,8 @@ class ruleset_builder { // These are the contents of the latest generated ruleset // Rules - indexer final_base_rules_; - indexer final_user_rules_; + indexer final_base_rules_; + indexer final_user_rules_; // Filters std::unordered_map> rule_filters_; diff --git a/src/target_address.hpp b/src/target_address.hpp new file mode 100644 index 000000000..354c47088 --- /dev/null +++ b/src/target_address.hpp @@ -0,0 +1,61 @@ +// 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 2024 Datadog, Inc. + +#pragma once + +#include "utils.hpp" + +#include +#include +#include +#include +#include + +namespace ddwaf { + +using target_index = std::size_t; + +inline target_index get_target_index(std::string_view address) +{ + return std::hash{}(address); +} + +struct target_address { + explicit target_address(std::string name_) + : name(std::move(name_)), index(get_target_index(name)) + {} + + bool operator<(const target_address &o) const { return name < o.name; } + bool operator>(const target_address &o) const { return name > o.name; } + bool operator==(const target_address &o) const { return name == o.name; } + + std::string name; + target_index index; +}; + +struct target_address_view { + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + target_address_view(const target_address &address) : name(address.name), index(address.index) {} + + bool operator<(const target_address_view &o) const { return name < o.name; } + bool operator>(const target_address_view &o) const { return name > o.name; } + bool operator==(const target_address_view &o) const { return name == o.name; } + + std::string_view name; + target_index index; +}; + +} // namespace ddwaf + +namespace std { +template <> struct hash { + size_t operator()(const ddwaf::target_address &addr) const { return addr.index; } +}; + +template <> struct hash { + size_t operator()(const ddwaf::target_address_view &addr) const { return addr.index; } +}; +} // namespace std diff --git a/src/utils.hpp b/src/utils.hpp index 0c53cdad0..a6f7860dd 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -56,13 +56,6 @@ struct object_limits { uint32_t max_transformers_per_address{10}; // can't be overridden for now }; -using target_index = std::size_t; - -inline target_index get_target_index(std::string_view address) -{ - return std::hash{}(address); -} - inline size_t find_string_cutoff(const char *str, size_t length, object_limits limits = {}) { // If the string is shorter than our cap, then fine diff --git a/tests/integration/context/test.cpp b/tests/integration/context/test.cpp index d3bad2465..c063a69aa 100644 --- a/tests/integration/context/test.cpp +++ b/tests/integration/context/test.cpp @@ -987,4 +987,54 @@ TEST(TestContextIntegration, MultipleModuleSingleCollectionMatch) ddwaf_destroy(handle); } +TEST(TestContextIntegration, TimeoutBeyondLimit) +{ + // Initialize a WAF rule + auto rule = read_file("processor.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + // Setup the parameter structure + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object subMap = DDWAF_OBJECT_MAP; + ddwaf_object tmp; + ddwaf_object_map_add(¶meter, "value", ddwaf_object_string(&tmp, "rule2")); + ddwaf_object_map_add(&subMap, "key", ddwaf_object_string(&tmp, "rule3")); + ddwaf_object_map_add(¶meter, "value2", &subMap); // ddwaf_object_string(&,"rule3")); + + ddwaf_result ret; + EXPECT_EQ(ddwaf_run(context, ¶meter, nullptr, &ret, std::numeric_limits::max()), + DDWAF_MATCH); + + EXPECT_FALSE(ret.timeout); + EXPECT_EVENTS(ret, {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = "rule2", + .highlight = "rule2", + .args = {{ + .value = "rule2", + .address = "value", + }}}, + {.op = "match_regex", + .op_value = "rule3", + .highlight = "rule3", + .args = {{ + .value = "rule3", + .address = "value2", + .path = {"key"}, + }}}}}); + + ddwaf_result_free(&ret); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + } // namespace diff --git a/tests/integration/diagnostics/v1/test.cpp b/tests/integration/diagnostics/v1/test.cpp index 241e40333..a2e504efc 100644 --- a/tests/integration/diagnostics/v1/test.cpp +++ b/tests/integration/diagnostics/v1/test.cpp @@ -66,7 +66,7 @@ void run_test(ddwaf_handle handle) ddwaf_context_destroy(context); } -TEST(TestTestDiagnosticsV1Integration, Basic) +TEST(TestDiagnosticsV1Integration, Basic) { auto rule = yaml_to_object( R"({version: '1.1', events: [{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operation: match_regex, parameters: {inputs: [arg1], regex: .*}}, {operation: match_regex, parameters: {inputs: [arg2:x], regex: .*}},{operation: match_regex, parameters: {inputs: [arg2:y], regex: .*}}]}]})"); @@ -101,7 +101,7 @@ TEST(TestTestDiagnosticsV1Integration, Basic) ddwaf_destroy(handle); } -TEST(TestTestDiagnosticsV1Integration, TestInvalidRule) +TEST(TestDiagnosticsV1Integration, TestInvalidRule) { auto rule = read_file("invalid_single_v1.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -139,7 +139,7 @@ TEST(TestTestDiagnosticsV1Integration, TestInvalidRule) ddwaf_object_free(&diagnostics); } -TEST(TestTestDiagnosticsV1Integration, TestMultipleSameInvalidRules) +TEST(TestDiagnosticsV1Integration, TestMultipleSameInvalidRules) { auto rule = read_file("invalid_multiple_same_v1.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -178,7 +178,7 @@ TEST(TestTestDiagnosticsV1Integration, TestMultipleSameInvalidRules) ddwaf_object_free(&diagnostics); } -TEST(TestTestDiagnosticsV1Integration, TestMultipleDiffInvalidRules) +TEST(TestDiagnosticsV1Integration, TestMultipleDiffInvalidRules) { auto rule = read_file("invalid_multiple_diff_v1.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -227,7 +227,7 @@ TEST(TestTestDiagnosticsV1Integration, TestMultipleDiffInvalidRules) ddwaf_object_free(&diagnostics); } -TEST(TestTestDiagnosticsV1Integration, TestMultipleMixInvalidRules) +TEST(TestDiagnosticsV1Integration, TestMultipleMixInvalidRules) { auto rule = read_file("invalid_multiple_mix_v1.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -287,7 +287,7 @@ TEST(TestTestDiagnosticsV1Integration, TestMultipleMixInvalidRules) ddwaf_destroy(handle); } -TEST(TestTestDiagnosticsV1Integration, TestInvalidDuplicate) +TEST(TestDiagnosticsV1Integration, TestInvalidDuplicate) { auto rule = read_file("invalid_duplicate_v1.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -326,7 +326,7 @@ TEST(TestTestDiagnosticsV1Integration, TestInvalidDuplicate) ddwaf_destroy(handle); } -TEST(TestTestDiagnosticsV1Integration, TestInvalidTooManyTransformers) +TEST(TestDiagnosticsV1Integration, TestInvalidTooManyTransformers) { auto rule = read_file("invalid_too_many_transformers_v1.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); diff --git a/tests/unit/clock_test.cpp b/tests/unit/clock_test.cpp index 7fba1ccc8..43a9a0686 100644 --- a/tests/unit/clock_test.cpp +++ b/tests/unit/clock_test.cpp @@ -17,7 +17,7 @@ namespace { TEST(TestTimer, Basic) { - ddwaf::timer deadline{2ms, 1}; + ddwaf::base_timer<1> deadline{2ms}; EXPECT_FALSE(deadline.expired()); std::this_thread::sleep_for(2ms); @@ -34,7 +34,7 @@ TEST(TestTimer, ExpiredFromConstruction) TEST(TestTimer, ValidatePeriod) { - ddwaf::timer deadline{1ms, 5}; + ddwaf::base_timer<5> deadline{1ms}; EXPECT_FALSE(deadline.expired()); std::this_thread::sleep_for(1ms); @@ -46,4 +46,23 @@ TEST(TestTimer, ValidatePeriod) EXPECT_TRUE(deadline.expired()); } +TEST(TestTimer, EndlessTimer) +{ + // Simple sanity check, we can't really test this + auto deadline = ddwaf::endless_timer(); + EXPECT_FALSE(deadline.expired()); + + std::this_thread::sleep_for(1ms); + EXPECT_FALSE(deadline.expired()); +} + +TEST(TestTimer, TimerExpirationBeyondSizeLimit) +{ + ddwaf::base_timer<5> deadline{std::chrono::nanoseconds::max()}; + EXPECT_FALSE(deadline.expired()); + + std::this_thread::sleep_for(1ms); + EXPECT_FALSE(deadline.expired()); +} + } // namespace diff --git a/tests/unit/collection_test.cpp b/tests/unit/collection_test.cpp deleted file mode 100644 index c41fc2f6d..000000000 --- a/tests/unit/collection_test.cpp +++ /dev/null @@ -1,630 +0,0 @@ -// 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 "collection.hpp" -#include "common/gtest_utils.hpp" -#include "condition/scalar_condition.hpp" -#include "matcher/exact_match.hpp" -#include "matcher/ip_match.hpp" - -using namespace ddwaf; -using namespace std::literals; - -namespace { - -template struct TestCollection : public testing::Test {}; - -// In the absence of actions, priority collections should behave as regular collections -using CollectionTypes = ::testing::Types; -TYPED_TEST_SUITE(TestCollection, CollectionTypes); - -// Validate that a rule within the collection matches only once -TYPED_TEST(TestCollection, SingleRuleMatch) -{ - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - - TypeParam rule_collection; - rule_collection.insert(rule); - - collection_cache cache; - ddwaf::object_store store; - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 1); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - - store.insert(root); - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 0); - } -} - -// Validate that once there's a match for a collection, a second match isn't possible -TYPED_TEST(TestCollection, MultipleRuleCachedMatch) -{ - std::vector> rules; - TypeParam rule_collection; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - rules.emplace_back(rule); - rule_collection.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - rules.emplace_back(rule); - rule_collection.insert(rule); - } - - ddwaf::object_store store; - collection_cache cache; - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 1); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 0); - } -} - -// Validate that after a failed match, the collection can still produce a match -TYPED_TEST(TestCollection, MultipleRuleFailAndMatch) -{ - std::vector> rules; - TypeParam rule_collection; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - rules.emplace_back(rule); - rule_collection.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - rules.emplace_back(rule); - rule_collection.insert(rule); - } - - ddwaf::object_store store; - collection_cache cache; - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admino")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 0); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 1); - } -} - -// Validate that the rule cache is acted on -TYPED_TEST(TestCollection, SingleRuleMultipleCalls) -{ - test::expression_builder builder(2); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - - TypeParam rule_collection; - rule_collection.insert(rule); - - collection_cache cache; - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - - ddwaf::object_store store; - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 0); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - - ddwaf::object_store store; - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - rule_collection.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 1); - } -} - -// Validate that a match in a priority collection prevents further regular matches -TEST(TestPriorityCollection, NoRegularMatchAfterPriorityMatch) -{ - std::vector> rules; - collection regular; - priority_collection priority; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - rules.emplace_back(rule); - regular.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - ddwaf::object_store store; - - collection_cache cache; - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 1); - ASSERT_EQ(events[0].rule->get_actions().size(), 1); - EXPECT_STREQ(events[0].rule->get_actions()[0].data(), "redirect"); - } - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - regular.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 0); - } -} - -// Validate that a match in a regular collection doesn't inhibit a match in a -// priority collection -TEST(TestPriorityCollection, PriorityMatchAfterRegularMatch) -{ - std::vector> rules; - collection regular; - priority_collection priority; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - rules.emplace_back(rule); - regular.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - ddwaf::object_store store; - - collection_cache cache; - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - regular.match(events, store, cache, {}, {}, deadline); - - EXPECT_EQ(events.size(), 1); - EXPECT_TRUE(events[0].rule->get_actions().empty()); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 1); - ASSERT_EQ(events[0].rule->get_actions().size(), 1); - EXPECT_STREQ(events[0].rule->get_actions()[0].data(), "redirect"); - } -} - -// Validate that a match in a priority collection prevents another match -TEST(TestPriorityCollection, NoPriorityMatchAfterPriorityMatch) -{ - std::vector> rules; - priority_collection priority; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - ddwaf::object_store store; - - collection_cache cache; - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 1); - ASSERT_EQ(events[0].rule->get_actions().size(), 1); - EXPECT_STREQ(events[0].rule->get_actions()[0].data(), "block"); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 0); - } -} - -// Validate that an ephemeral match in a priority collection doesn't another match -TEST(TestPriorityCollection, NoPriorityMatchAfterEphemeralPriorityMatch) -{ - std::vector> rules; - priority_collection priority; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - ddwaf::object_store store; - - collection_cache cache; - { - auto scope = store.get_eval_scope(); - - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root, object_store::attribute::ephemeral); - - std::vector events; - ddwaf::timer deadline{2s}; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 1); - ASSERT_EQ(events[0].rule->get_actions().size(), 1); - EXPECT_STREQ(events[0].rule->get_actions()[0].data(), "block"); - } - - { - auto scope = store.get_eval_scope(); - - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - store.insert(root); - - std::vector events; - ddwaf::timer deadline{2s}; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 1); - } -} - -// Validate that an ephemeral match in a priority collection prevents another match -// within the same evaluation -TEST(TestPriorityCollection, EphemeralPriorityMatchNoOtherMatches) -{ - std::vector> rules; - priority_collection priority; - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - rules.emplace_back(rule); - priority.insert(rule); - } - - ddwaf::timer deadline{2s}; - ddwaf::object_store store; - - collection_cache cache; - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - store.insert(root, object_store::attribute::ephemeral); - } - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - store.insert(root); - } - - std::vector events; - priority.match(events, store, cache, {}, {}, deadline); - - ASSERT_EQ(events.size(), 1); - ASSERT_EQ(events[0].rule->get_actions().size(), 1); - EXPECT_STREQ(events[0].rule->get_actions()[0].data(), "block"); -} - -} // namespace diff --git a/tests/unit/context_test.cpp b/tests/unit/context_test.cpp index 2d893a8df..47f2537be 100644 --- a/tests/unit/context_test.cpp +++ b/tests/unit/context_test.cpp @@ -43,20 +43,20 @@ namespace { namespace mock { -class rule : public ddwaf::rule { +class rule : public core_rule { public: using ptr = std::shared_ptr; rule(std::string id, std::string name, std::unordered_map tags, std::shared_ptr expr, std::vector actions = {}, bool enabled = true, source_type source = source_type::base) - : ddwaf::rule(std::move(id), std::move(name), std::move(tags), std::move(expr), + : core_rule(std::move(id), std::move(name), std::move(tags), std::move(expr), std::move(actions), enabled, source) {} ~rule() override = default; MOCK_METHOD(std::optional, match, - (const object_store &, rule::cache_type &, (const exclusion::object_set_ref &objects), + (const object_store &, core_rule::cache_type &, (const exclusion::object_set_ref &objects), (const std::unordered_map> &), ddwaf::timer &), (const override)); @@ -67,7 +67,7 @@ class rule_filter : public ddwaf::exclusion::rule_filter { using ptr = std::shared_ptr; rule_filter(std::string id, std::shared_ptr expr, - std::set rule_targets, filter_mode mode = filter_mode::bypass) + std::set rule_targets, filter_mode mode = filter_mode::bypass) : exclusion::rule_filter(std::move(id), std::move(expr), std::move(rule_targets), mode) {} ~rule_filter() override = default; @@ -84,7 +84,7 @@ class input_filter : public ddwaf::exclusion::input_filter { using ptr = std::shared_ptr; input_filter(std::string id, std::shared_ptr expr, - std::set rule_targets, std::shared_ptr filter) + std::set rule_targets, std::shared_ptr filter) : exclusion::input_filter( std::move(id), std::move(expr), std::move(rule_targets), std::move(filter)) {} @@ -132,7 +132,7 @@ TEST(TestContext, PreprocessorEval) EXPECT_CALL(*rule, match(_, _, _, _, _)).InSequence(seq).WillOnce(Return(std::nullopt)); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ruleset->preprocessors.emplace("id", proc); ddwaf::context ctx(ruleset); @@ -164,7 +164,7 @@ TEST(TestContext, PostprocessorEval) EXPECT_CALL(*proc, eval(_, _, _, _)).InSequence(seq); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ruleset->postprocessors.emplace("id", proc); ddwaf::context ctx(ruleset); @@ -190,7 +190,7 @@ TEST(TestContext, SkipRuleNoTargets) auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); EXPECT_CALL(*rule, match(_, _, _, _, _)).Times(0); @@ -214,10 +214,10 @@ TEST(TestContext, MatchTimeout) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ddwaf::timer deadline{0s}; ddwaf::test::context ctx(ruleset); @@ -241,10 +241,10 @@ TEST(TestContext, NoMatch) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -269,10 +269,10 @@ TEST(TestContext, Match) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -290,6 +290,7 @@ TEST(TestContext, Match) TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -300,9 +301,8 @@ TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category1"}}; - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id1", "name1", std::move(tags), builder.build())); } { @@ -315,11 +315,12 @@ TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id2", "name2", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); + ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -334,8 +335,8 @@ TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) EXPECT_EQ(events.size(), 1); auto &event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id1"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name1"); + EXPECT_STREQ(event.rule->get_id().data(), "id1"); + EXPECT_STREQ(event.rule->get_name().data(), "name1"); EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); EXPECT_STREQ(event.rule->get_tag("category").data(), "category1"); std::vector expected_actions{}; @@ -354,6 +355,7 @@ TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -364,9 +366,8 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category1"}}; - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id1", "name1", std::move(tags), builder.build())); } { @@ -379,11 +380,11 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"block"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared( + "id2", "name2", std::move(tags), builder.build(), std::vector{"block"})); + rules.back()->set_verdict(core_rule::verdict_type::block); } + ruleset->insert_rules(rules, {}); { ddwaf::test::context ctx(ruleset); @@ -400,7 +401,7 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) EXPECT_EQ(events.size(), 1); auto event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id2"); + EXPECT_STREQ(event.rule->get_id().data(), "id2"); EXPECT_EQ(event.rule->get_actions().size(), 1); EXPECT_STREQ(event.rule->get_actions()[0].data(), "block"); } @@ -420,7 +421,7 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) EXPECT_EQ(events.size(), 1); auto event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id2"); + EXPECT_STREQ(event.rule->get_id().data(), "id2"); EXPECT_EQ(event.rule->get_actions().size(), 1); EXPECT_STREQ(event.rule->get_actions()[0].data(), "block"); } @@ -429,6 +430,7 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -439,9 +441,8 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category1"}}; - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id1", "name1", std::move(tags), builder.build())); } { @@ -454,10 +455,10 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id2", "name2", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -473,8 +474,8 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) EXPECT_EQ(events.size(), 1); auto &event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id1"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name1"); + EXPECT_STREQ(event.rule->get_id().data(), "id1"); + EXPECT_STREQ(event.rule->get_name().data(), "name1"); EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); EXPECT_STREQ(event.rule->get_tag("category").data(), "category1"); std::vector expected_actions{}; @@ -505,6 +506,7 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -515,9 +517,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) std::unordered_map tags{ {"type", "type"}, {"category", "category1"}}; - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id1", "name1", std::move(tags), builder.build())); } { @@ -530,11 +531,11 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"block"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared( + "id2", "name2", std::move(tags), builder.build(), std::vector{"block"})); + rules.back()->set_verdict(core_rule::verdict_type::block); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -550,8 +551,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) EXPECT_EQ(events.size(), 1); auto &event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id1"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name1"); + EXPECT_STREQ(event.rule->get_id().data(), "id1"); + EXPECT_STREQ(event.rule->get_name().data(), "name1"); EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); EXPECT_STREQ(event.rule->get_tag("category").data(), "category1"); std::vector expected_actions{}; @@ -581,8 +582,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) auto &event = events[0]; EXPECT_EQ(events.size(), 1); - EXPECT_STREQ(event.rule->get_id().c_str(), "id2"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name2"); + EXPECT_STREQ(event.rule->get_id().data(), "id2"); + EXPECT_STREQ(event.rule->get_name().data(), "name2"); EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); EXPECT_STREQ(event.rule->get_tag("category").data(), "category2"); std::vector expected_actions{"block"}; @@ -602,6 +603,7 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -612,10 +614,9 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) std::unordered_map tags{ {"type", "type"}, {"category", "category1"}}; - auto rule = std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared( + "id1", "name1", std::move(tags), builder.build(), std::vector{"block"})); + rules.back()->set_verdict(core_rule::verdict_type::block); } { @@ -628,10 +629,10 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id2", "name2", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -647,8 +648,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) EXPECT_EQ(events.size(), 1); auto &event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id1"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name1"); + EXPECT_STREQ(event.rule->get_id().data(), "id1"); + EXPECT_STREQ(event.rule->get_name().data(), "name1"); EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); EXPECT_STREQ(event.rule->get_tag("category").data(), "category1"); std::vector expected_actions{"block"}; @@ -678,104 +679,10 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) } } -TEST(TestContext, MatchMultipleRulesWithPriorityUntilAllActionsMet) -{ - auto ruleset = test::get_default_ruleset(); - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("http.client_ip"); - builder.end_condition(std::vector{"192.168.0.1"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category1"}}; - - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); - } - - { - test::expression_builder builder(1); - builder.start_condition(); - builder.add_argument(); - builder.add_target("usr.id"); - builder.end_condition(std::vector{"admin"}); - - std::unordered_map tags{ - {"type", "type"}, {"category", "category2"}}; - - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - ruleset->insert_rule(rule); - } - - ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset); - - { - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); - ctx.insert(root); - - auto events = ctx.eval_rules({}, deadline); - EXPECT_EQ(events.size(), 1); - - auto &event = events[0]; - EXPECT_STREQ(event.rule->get_id().c_str(), "id1"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name1"); - EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); - EXPECT_STREQ(event.rule->get_tag("category").data(), "category1"); - EXPECT_TRUE(event.rule->get_actions().empty()); - - auto &match = event.matches[0]; - EXPECT_STREQ(match.args[0].resolved.c_str(), "192.168.0.1"); - EXPECT_STREQ(match.highlights[0].c_str(), "192.168.0.1"); - EXPECT_STREQ(match.operator_name.data(), "ip_match"); - EXPECT_STREQ(match.operator_value.data(), ""); - EXPECT_STREQ(match.args[0].address.data(), "http.client_ip"); - EXPECT_TRUE(match.args[0].key_path.empty()); - } - - { - // An existing match in a collection will not inhibit a match in a - // priority collection. - ddwaf_object root; - ddwaf_object tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); - ctx.insert(root); - - auto events = ctx.eval_rules({}, deadline); - EXPECT_EQ(events.size(), 1); - - auto &event = events[0]; - EXPECT_EQ(events.size(), 1); - EXPECT_STREQ(event.rule->get_id().c_str(), "id2"); - EXPECT_STREQ(event.rule->get_name().c_str(), "name2"); - EXPECT_STREQ(event.rule->get_tag("type").data(), "type"); - EXPECT_STREQ(event.rule->get_tag("category").data(), "category2"); - std::vector expected_actions{"redirect"}; - EXPECT_EQ(event.rule->get_actions(), expected_actions); - EXPECT_EQ(event.matches.size(), 1); - - auto &match = event.matches[0]; - EXPECT_STREQ(match.args[0].resolved.c_str(), "admin"); - EXPECT_STREQ(match.highlights[0].c_str(), "admin"); - EXPECT_STREQ(match.operator_name.data(), "exact_match"); - EXPECT_STREQ(match.operator_value.data(), ""); - EXPECT_STREQ(match.args[0].address.data(), "usr.id"); - EXPECT_TRUE(match.args[0].key_path.empty()); - } -} - TEST(TestContext, MatchMultipleCollectionsSingleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -786,9 +693,8 @@ TEST(TestContext, MatchMultipleCollectionsSingleRun) std::unordered_map tags{ {"type", "type1"}, {"category", "category1"}}; - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id1", "name1", std::move(tags), builder.build())); } { @@ -801,10 +707,10 @@ TEST(TestContext, MatchMultipleCollectionsSingleRun) std::unordered_map tags{ {"type", "type2"}, {"category", "category2"}}; - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id2", "name2", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -820,9 +726,10 @@ TEST(TestContext, MatchMultipleCollectionsSingleRun) EXPECT_EQ(events.size(), 2); } -TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) +TEST(TestContext, MatchPriorityCollectionsSingleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -833,10 +740,9 @@ TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) std::unordered_map tags{ {"type", "type1"}, {"category", "category1"}}; - auto rule = std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared( + "id1", "name1", std::move(tags), builder.build(), std::vector{"block"})); + rules.back()->set_verdict(core_rule::verdict_type::block); } { @@ -849,11 +755,10 @@ TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) std::unordered_map tags{ {"type", "type2"}, {"category", "category2"}}; - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared("id2", "name2", std::move(tags), + builder.build(), std::vector{"redirect"})); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -866,12 +771,13 @@ TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) ctx.insert(root); auto events = ctx.eval_rules({}, deadline); - EXPECT_EQ(events.size(), 2); + EXPECT_EQ(events.size(), 1); } TEST(TestContext, MatchMultipleCollectionsDoubleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -882,9 +788,8 @@ TEST(TestContext, MatchMultipleCollectionsDoubleRun) std::unordered_map tags{ {"type", "type1"}, {"category", "category1"}}; - auto rule = std::make_shared("id1", "name1", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id1", "name1", std::move(tags), builder.build())); } { @@ -897,10 +802,10 @@ TEST(TestContext, MatchMultipleCollectionsDoubleRun) std::unordered_map tags{ {"type", "type2"}, {"category", "category2"}}; - auto rule = std::make_shared("id2", "name2", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("id2", "name2", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -931,6 +836,7 @@ TEST(TestContext, MatchMultipleCollectionsDoubleRun) TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -941,10 +847,9 @@ TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) std::unordered_map tags{ {"type", "type1"}, {"category", "category1"}}; - auto rule = std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared( + "id1", "name1", std::move(tags), builder.build(), std::vector{"block"})); + rules.back()->set_verdict(core_rule::verdict_type::block); } { @@ -957,11 +862,10 @@ TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) std::unordered_map tags{ {"type", "type2"}, {"category", "category2"}}; - auto rule = std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"redirect"}); - - ruleset->insert_rule(rule); + rules.emplace_back(std::make_shared("id2", "name2", std::move(tags), + builder.build(), std::vector{"redirect"})); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -1008,7 +912,7 @@ TEST(TestContext, SkipRuleFilterNoTargets) rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1020,7 +924,7 @@ TEST(TestContext, SkipRuleFilterNoTargets) builder.end_condition(std::vector{"192.168.0.1"}); filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + "1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1057,7 +961,7 @@ TEST(TestContext, SkipRuleButNotRuleFilterNoTargets) rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1069,7 +973,7 @@ TEST(TestContext, SkipRuleButNotRuleFilterNoTargets) builder.end_condition(std::vector{"192.168.0.1"}); filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + "1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1092,7 +996,7 @@ TEST(TestContext, RuleFilterWithCondition) auto ruleset = test::get_default_ruleset(); // Generate rule - std::shared_ptr rule; + std::shared_ptr rule; { test::expression_builder builder(1); builder.start_condition(); @@ -1103,9 +1007,9 @@ TEST(TestContext, RuleFilterWithCondition) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); + rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1116,8 +1020,8 @@ TEST(TestContext, RuleFilterWithCondition) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + auto filter = + std::make_shared("1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1144,7 +1048,7 @@ TEST(TestContext, RuleFilterWithEphemeralConditionMatch) auto ruleset = test::get_default_ruleset(); // Generate rule - std::shared_ptr rule; + std::shared_ptr rule; { test::expression_builder builder(1); builder.start_condition(); @@ -1155,9 +1059,9 @@ TEST(TestContext, RuleFilterWithEphemeralConditionMatch) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); + rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1168,8 +1072,8 @@ TEST(TestContext, RuleFilterWithEphemeralConditionMatch) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + auto filter = + std::make_shared("1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1205,7 +1109,7 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralBypassPersistentMonitor) auto ruleset = test::get_default_ruleset(); // Generate rule - std::shared_ptr rule; + std::shared_ptr rule; { test::expression_builder builder(1); builder.start_condition(); @@ -1216,9 +1120,10 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralBypassPersistentMonitor) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); + rule = std::make_shared("id", "name", std::move(tags), builder.build()); rule->set_actions({"block"}); - ruleset->insert_rule(rule); + rule->set_verdict(core_rule::verdict_type::block); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1229,8 +1134,8 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralBypassPersistentMonitor) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + auto filter = + std::make_shared("1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1242,7 +1147,7 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralBypassPersistentMonitor) builder.end_condition(std::vector{"unrouted"}); auto filter = std::make_shared("2", builder.build(), - std::set{rule.get()}, exclusion::filter_mode::monitor); + std::set{rule.get()}, exclusion::filter_mode::monitor); ruleset->insert_filter(filter); } @@ -1282,7 +1187,7 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralMonitorPersistentBypass) auto ruleset = test::get_default_ruleset(); // Generate rule - std::shared_ptr rule; + std::shared_ptr rule; { test::expression_builder builder(1); builder.start_condition(); @@ -1293,9 +1198,10 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralMonitorPersistentBypass) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); + rule = std::make_shared("id", "name", std::move(tags), builder.build()); rule->set_actions({"block"}); - ruleset->insert_rule(rule); + rule->set_verdict(core_rule::verdict_type::block); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1307,7 +1213,7 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralMonitorPersistentBypass) builder.end_condition(std::vector{"192.168.0.1"}); auto filter = std::make_shared("1", builder.build(), - std::set{rule.get()}, exclusion::filter_mode::monitor); + std::set{rule.get()}, exclusion::filter_mode::monitor); ruleset->insert_filter(filter); } @@ -1318,8 +1224,8 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralMonitorPersistentBypass) builder.add_target("http.route"); builder.end_condition(std::vector{"unrouted"}); - auto filter = std::make_shared( - "2", builder.build(), std::set{rule.get()}); + auto filter = + std::make_shared("2", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1356,7 +1262,7 @@ TEST(TestContext, RuleFilterTimeout) auto ruleset = test::get_default_ruleset(); // Generate rule - std::shared_ptr rule; + std::shared_ptr rule; { test::expression_builder builder(1); builder.start_condition(); @@ -1367,9 +1273,9 @@ TEST(TestContext, RuleFilterTimeout) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); + rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1380,8 +1286,8 @@ TEST(TestContext, RuleFilterTimeout) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + auto filter = + std::make_shared("1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1403,7 +1309,7 @@ TEST(TestContext, NoRuleFilterWithCondition) auto ruleset = test::get_default_ruleset(); // Generate rule - std::shared_ptr rule; + std::shared_ptr rule; { test::expression_builder builder(1); builder.start_condition(); @@ -1414,9 +1320,9 @@ TEST(TestContext, NoRuleFilterWithCondition) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); + rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1427,8 +1333,8 @@ TEST(TestContext, NoRuleFilterWithCondition) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto filter = std::make_shared( - "1", builder.build(), std::set{rule.get()}); + auto filter = + std::make_shared("1", builder.build(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -1455,18 +1361,17 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) // Generate rule constexpr unsigned num_rules = 9; - std::vector> rules; + std::vector> rules; rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rules.emplace_back(std::make_shared("id" + std::to_string(i), "name", + rules.emplace_back(std::make_shared("id" + std::to_string(i), "name", std::move(tags), std::make_shared(), std::vector{})); - - ruleset->insert_rule(rules.back()); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -1478,7 +1383,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { auto filter = std::make_shared("1", std::make_shared(), - std::set{rules[0].get(), rules[1].get(), rules[2].get()}); + std::set{rules[0].get(), rules[1].get(), rules[2].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1490,7 +1395,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { auto filter = std::make_shared("2", std::make_shared(), - std::set{rules[3].get(), rules[4].get(), rules[5].get()}); + std::set{rules[3].get(), rules[4].get(), rules[5].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1505,7 +1410,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { auto filter = std::make_shared("3", std::make_shared(), - std::set{rules[6].get(), rules[7].get(), rules[8].get()}); + std::set{rules[6].get(), rules[7].get(), rules[8].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1528,7 +1433,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) // Generate rule constexpr unsigned num_rules = 9; - std::vector> rules; + std::vector> rules; rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::string id = "id" + std::to_string(i); @@ -1536,11 +1441,10 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), + rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), std::make_shared(), std::vector{})); - - ruleset->insert_rule(rules.back()); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -1552,8 +1456,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("1", std::make_shared(), - std::set{ - rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get()}); + std::set{rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1566,7 +1469,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("2", std::make_shared(), - std::set{rules[2].get(), rules[3].get(), rules[4].get()}); + std::set{rules[2].get(), rules[3].get(), rules[4].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1580,7 +1483,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("3", std::make_shared(), - std::set{rules[0].get(), rules[5].get(), rules[6].get()}); + std::set{rules[0].get(), rules[5].get(), rules[6].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1596,7 +1499,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("4", std::make_shared(), - std::set{rules[7].get(), rules[8].get(), rules[6].get()}); + std::set{rules[7].get(), rules[8].get(), rules[6].get()}); ruleset->insert_filter(filter); auto rules_to_exclude = ctx.eval_filters(deadline); @@ -1614,7 +1517,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("5", std::make_shared(), - std::set{rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get(), + std::set{rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get(), rules[4].get(), rules[5].get(), rules[6].get(), rules[7].get(), rules[8].get()}); ruleset->insert_filter(filter); @@ -1638,7 +1541,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) // Generate rule constexpr unsigned num_rules = 10; - std::vector> rules; + std::vector> rules; rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::string id = "id" + std::to_string(i); @@ -1646,11 +1549,10 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), + rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), std::make_shared(), std::vector{})); - - ruleset->insert_rule(rules.back()); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -1663,7 +1565,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) builder.end_condition(std::vector{"192.168.0.1"}); auto filter = std::make_shared("1", builder.build(), - std::set{ + std::set{ rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get(), rules[4].get()}); ruleset->insert_filter(filter); } @@ -1676,7 +1578,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) builder.end_condition(std::vector{"admin"}); auto filter = std::make_shared("2", builder.build(), - std::set{ + std::set{ rules[5].get(), rules[6].get(), rules[7].get(), rules[8].get(), rules[9].get()}); ruleset->insert_filter(filter); } @@ -1725,7 +1627,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) // Generate rule constexpr unsigned num_rules = 10; - std::vector> rules; + std::vector> rules; rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::string id = "id" + std::to_string(i); @@ -1733,11 +1635,10 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), + rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), std::make_shared(), std::vector{})); - - ruleset->insert_rule(rules.back()); } + ruleset->insert_rules(rules, {}); ddwaf::timer deadline{2s}; ddwaf::test::context ctx(ruleset); @@ -1750,7 +1651,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) builder.end_condition(std::vector{"192.168.0.1"}); auto filter = std::make_shared("1", builder.build(), - std::set{rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get(), + std::set{rules[0].get(), rules[1].get(), rules[2].get(), rules[3].get(), rules[4].get(), rules[5].get(), rules[6].get()}); ruleset->insert_filter(filter); } @@ -1763,7 +1664,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) builder.end_condition(std::vector{"admin"}); auto filter = std::make_shared("2", builder.build(), - std::set{rules[3].get(), rules[4].get(), rules[5].get(), rules[6].get(), + std::set{rules[3].get(), rules[4].get(), rules[5].get(), rules[6].get(), rules[7].get(), rules[8].get(), rules[9].get()}); ruleset->insert_filter(filter); } @@ -1827,7 +1728,7 @@ TEST(TestContext, SkipInputFilterNoTargets) rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1835,7 +1736,7 @@ TEST(TestContext, SkipInputFilterNoTargets) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); ruleset->insert_filter(filter); @@ -1873,7 +1774,7 @@ TEST(TestContext, SkipRuleButNotInputFilterNoTargets) rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } // Generate filter @@ -1881,7 +1782,7 @@ TEST(TestContext, SkipRuleButNotInputFilterNoTargets) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); ruleset->insert_filter(filter); @@ -1910,17 +1811,17 @@ TEST(TestContext, InputFilterExclude) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ruleset->insert_filter(filter); ddwaf::timer deadline{2s}; @@ -1950,17 +1851,17 @@ TEST(TestContext, InputFilterExcludeEphemeral) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ruleset->insert_filter(filter); ddwaf::test::context ctx(ruleset); @@ -2001,17 +1902,17 @@ TEST(TestContext, InputFilterExcludeEphemeralReuseObject) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); auto ruleset = test::get_default_ruleset(); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); ruleset->insert_filter(filter); ruleset->free_fn = nullptr; @@ -2045,14 +1946,14 @@ TEST(TestContext, InputFilterExcludeRule) auto ruleset = test::get_default_ruleset(); - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + ruleset->insert_rules({rule}, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2061,7 +1962,7 @@ TEST(TestContext, InputFilterExcludeRule) { auto filter = std::make_shared( - "1", std::make_shared(), std::set{rule.get()}); + "1", std::make_shared(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -2100,14 +2001,14 @@ TEST(TestContext, InputFilterExcludeRuleEphemeral) auto ruleset = test::get_default_ruleset(); - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + ruleset->insert_rules({rule}, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2116,7 +2017,7 @@ TEST(TestContext, InputFilterExcludeRuleEphemeral) { auto filter = std::make_shared( - "1", std::make_shared(), std::set{rule.get()}); + "1", std::make_shared(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -2150,14 +2051,14 @@ TEST(TestContext, InputFilterMonitorRuleEphemeral) auto ruleset = test::get_default_ruleset(); - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + ruleset->insert_rules({rule}, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2166,7 +2067,7 @@ TEST(TestContext, InputFilterMonitorRuleEphemeral) { auto filter = std::make_shared("1", std::make_shared(), - std::set{rule.get()}, filter_mode::monitor); + std::set{rule.get()}, filter_mode::monitor); ruleset->insert_filter(filter); } @@ -2205,15 +2106,15 @@ TEST(TestContext, InputFilterExcluderRuleEphemeralAndPersistent) auto ruleset = test::get_default_ruleset(); - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + ruleset->insert_rules({rule}, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); obj_filter->insert(get_target_index("usr.id"), "usr.id"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2222,7 +2123,7 @@ TEST(TestContext, InputFilterExcluderRuleEphemeralAndPersistent) { auto filter = std::make_shared( - "1", std::make_shared(), std::set{rule.get()}); + "1", std::make_shared(), std::set{rule.get()}); ruleset->insert_filter(filter); } @@ -2266,15 +2167,15 @@ TEST(TestContext, InputFilterMonitorRuleEphemeralAndPersistent) auto ruleset = test::get_default_ruleset(); - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + ruleset->insert_rules({rule}, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); obj_filter->insert(get_target_index("usr.id"), "usr.id"); - std::set eval_filters{rule.get()}; + std::set eval_filters{rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2283,7 +2184,7 @@ TEST(TestContext, InputFilterMonitorRuleEphemeralAndPersistent) { auto filter = std::make_shared("1", std::make_shared(), - std::set{rule.get()}, filter_mode::monitor); + std::set{rule.get()}, filter_mode::monitor); ruleset->insert_filter(filter); } @@ -2333,9 +2234,9 @@ TEST(TestContext, InputFilterWithCondition) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } { @@ -2348,7 +2249,7 @@ TEST(TestContext, InputFilterWithCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{ruleset->rules[0].get()}; + std::set eval_filters{ruleset->rules[0].get()}; auto filter = std::make_shared( "1", builder.build(), std::move(eval_filters), std::move(obj_filter)); @@ -2422,9 +2323,9 @@ TEST(TestContext, InputFilterWithEphemeralCondition) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); - ruleset->insert_rule(rule); + ruleset->insert_rules({rule}, {}); } { @@ -2437,7 +2338,7 @@ TEST(TestContext, InputFilterWithEphemeralCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{ruleset->rules[0].get()}; + std::set eval_filters{ruleset->rules[0].get()}; auto filter = std::make_shared( "1", builder.build(), std::move(eval_filters), std::move(obj_filter)); @@ -2472,6 +2373,7 @@ TEST(TestContext, InputFilterWithEphemeralCondition) TEST(TestContext, InputFilterMultipleRules) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -2482,10 +2384,8 @@ TEST(TestContext, InputFilterMultipleRules) std::unordered_map tags{ {"type", "ip_type"}, {"category", "category"}}; - auto rule = - std::make_shared("ip_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("ip_id", "name", std::move(tags), builder.build())); } { @@ -2498,18 +2398,17 @@ TEST(TestContext, InputFilterMultipleRules) std::unordered_map tags{ {"type", "usr_type"}, {"category", "category"}}; - auto rule = - std::make_shared("usr_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("usr_id", "name", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); obj_filter->insert(get_target_index("usr.id"), "usr.id"); - std::set eval_filters{ruleset->rules[0].get(), ruleset->rules[1].get()}; + std::set eval_filters{ruleset->rules[0].get(), ruleset->rules[1].get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2585,6 +2484,7 @@ TEST(TestContext, InputFilterMultipleRules) TEST(TestContext, InputFilterMultipleRulesMultipleFilters) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -2595,10 +2495,8 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) std::unordered_map tags{ {"type", "ip_type"}, {"category", "category"}}; - auto rule = - std::make_shared("ip_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("ip_id", "name", std::move(tags), builder.build())); } { @@ -2611,17 +2509,16 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) std::unordered_map tags{ {"type", "usr_type"}, {"category", "category"}}; - auto rule = - std::make_shared("usr_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("usr_id", "name", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); { auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{ruleset->rules[0].get()}; + std::set eval_filters{ruleset->rules[0].get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2632,7 +2529,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("usr.id"), "usr.id"); - std::set eval_filters{ruleset->rules[1].get()}; + std::set eval_filters{ruleset->rules[1].get()}; auto filter = std::make_shared( "2", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2711,6 +2608,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { auto ruleset = test::get_default_ruleset(); + std::vector> rules; { test::expression_builder builder(1); builder.start_condition(); @@ -2721,10 +2619,8 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) std::unordered_map tags{ {"type", "ip_type"}, {"category", "category"}}; - auto rule = - std::make_shared("ip_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("ip_id", "name", std::move(tags), builder.build())); } { @@ -2737,10 +2633,8 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) std::unordered_map tags{ {"type", "usr_type"}, {"category", "category"}}; - auto rule = - std::make_shared("usr_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("usr_id", "name", std::move(tags), builder.build())); } { @@ -2753,11 +2647,10 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) std::unordered_map tags{ {"type", "cookie_type"}, {"category", "category"}}; - auto rule = - std::make_shared("cookie_id", "name", std::move(tags), builder.build()); - - ruleset->insert_rule(rule); + rules.emplace_back( + std::make_shared("cookie_id", "name", std::move(tags), builder.build())); } + ruleset->insert_rules(rules, {}); auto ip_rule = ruleset->rules[0]; auto usr_rule = ruleset->rules[1]; @@ -2768,7 +2661,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); obj_filter->insert(get_target_index("server.request.headers"), "server.request.headers"); - std::set eval_filters{ip_rule.get(), cookie_rule.get()}; + std::set eval_filters{ip_rule.get(), cookie_rule.get()}; auto filter = std::make_shared( "1", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2780,7 +2673,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) obj_filter->insert(get_target_index("usr.id"), "usr.id"); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - std::set eval_filters{usr_rule.get(), ip_rule.get()}; + std::set eval_filters{usr_rule.get(), ip_rule.get()}; auto filter = std::make_shared( "2", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); @@ -2792,7 +2685,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) obj_filter->insert(get_target_index("usr.id"), "usr.id"); obj_filter->insert(get_target_index("server.request.headers"), "server.request.headers"); - std::set eval_filters{usr_rule.get(), cookie_rule.get()}; + std::set eval_filters{usr_rule.get(), cookie_rule.get()}; auto filter = std::make_shared( "3", std::make_shared(), std::move(eval_filters), std::move(obj_filter)); diff --git a/tests/unit/event_serializer_test.cpp b/tests/unit/event_serializer_test.cpp index 43cdee345..5d644bdc5 100644 --- a/tests/unit/event_serializer_test.cpp +++ b/tests/unit/event_serializer_test.cpp @@ -41,7 +41,7 @@ TEST(TestEventSerializer, SerializeEmptyEvent) TEST(TestEventSerializer, SerializeSingleEventSingleMatch) { - ddwaf::rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, std::make_shared(), {"block", "monitor_request"}}; ddwaf::event event; @@ -78,7 +78,7 @@ TEST(TestEventSerializer, SerializeSingleEventSingleMatch) TEST(TestEventSerializer, SerializeSingleEventMultipleMatches) { - ddwaf::rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, std::make_shared(), {"block", "monitor_request"}}; ddwaf::event event; @@ -153,9 +153,9 @@ TEST(TestEventSerializer, SerializeMultipleEvents) ddwaf::obfuscator obfuscator; ddwaf::event_serializer serializer(obfuscator, actions); - ddwaf::rule rule1{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, + core_rule rule1{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, std::make_shared(), {"block", "monitor_request"}}; - ddwaf::rule rule2{"xasd1023", "pseudorandom rule", {{"type", "test"}, {"category", "none"}}, + core_rule rule2{"xasd1023", "pseudorandom rule", {{"type", "test"}, {"category", "none"}}, std::make_shared(), {"unblock"}}; std::vector events; { @@ -227,7 +227,7 @@ TEST(TestEventSerializer, SerializeMultipleEvents) TEST(TestEventSerializer, SerializeEventNoActions) { - ddwaf::rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}}, std::make_shared()}; ddwaf::event event; @@ -265,7 +265,7 @@ TEST(TestEventSerializer, SerializeEventNoActions) TEST(TestEventSerializer, SerializeAllTags) { - ddwaf::rule rule{"xasd1022", "random rule", + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}, {"tag0", "value0"}, {"tag1", "value1"}, {"confidence", "none"}}, std::make_shared(), {"unblock"}}; @@ -307,7 +307,7 @@ TEST(TestEventSerializer, SerializeAllTags) TEST(TestEventSerializer, NoMonitorActions) { - ddwaf::rule rule{"xasd1022", "random rule", + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}, {"tag0", "value0"}, {"tag1", "value1"}, {"confidence", "none"}}, std::make_shared(), {"monitor"}}; @@ -347,7 +347,7 @@ TEST(TestEventSerializer, NoMonitorActions) TEST(TestEventSerializer, UndefinedActions) { - ddwaf::rule rule{"xasd1022", "random rule", + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}, {"tag0", "value0"}, {"tag1", "value1"}, {"confidence", "none"}}, std::make_shared(), {"unblock_request"}}; @@ -387,7 +387,7 @@ TEST(TestEventSerializer, UndefinedActions) TEST(TestEventSerializer, StackTraceAction) { - ddwaf::rule rule{"xasd1022", "random rule", + core_rule rule{"xasd1022", "random rule", {{"type", "test"}, {"category", "none"}, {"tag0", "value0"}, {"tag1", "value1"}, {"confidence", "none"}}, std::make_shared(), {"stack_trace"}}; diff --git a/tests/unit/exclusion/input_filter_test.cpp b/tests/unit/exclusion/input_filter_test.cpp index fa8310268..32ef2d060 100644 --- a/tests/unit/exclusion/input_filter_test.cpp +++ b/tests/unit/exclusion/input_filter_test.cpp @@ -26,8 +26,7 @@ TEST(TestInputFilter, InputExclusionNoConditions) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter( "filter", std::make_shared(), {rule.get()}, std::move(obj_filter)); @@ -55,8 +54,7 @@ TEST(TestInputFilter, EphemeralInputExclusionNoConditions) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter( "filter", std::make_shared(), {rule.get()}, std::move(obj_filter)); @@ -89,8 +87,7 @@ TEST(TestInputFilter, ObjectExclusionNoConditions) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter( "filter", std::make_shared(), {rule.get()}, std::move(obj_filter)); @@ -123,8 +120,7 @@ TEST(TestInputFilter, EphemeralObjectExclusionNoConditions) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter( "filter", std::make_shared(), {rule.get()}, std::move(obj_filter)); @@ -158,8 +154,7 @@ TEST(TestInputFilter, PersistentInputExclusionWithPersistentCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -192,8 +187,7 @@ TEST(TestInputFilter, EphemeralInputExclusionWithEphemeralCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -230,8 +224,7 @@ TEST(TestInputFilter, PersistentInputExclusionWithEphemeralCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -268,8 +261,7 @@ TEST(TestInputFilter, EphemeralInputExclusionWithPersistentCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -302,8 +294,7 @@ TEST(TestInputFilter, InputExclusionWithConditionAndTransformers) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("usr.id"), "usr.id", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -335,8 +326,7 @@ TEST(TestInputFilter, InputExclusionFailedCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -370,8 +360,7 @@ TEST(TestInputFilter, ObjectExclusionWithCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -409,8 +398,7 @@ TEST(TestInputFilter, ObjectExclusionFailedCondition) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); ddwaf::timer deadline{2s}; @@ -435,8 +423,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("usr.id"), "usr.id"); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // To validate that the cache works, we pass an object store containing @@ -485,8 +472,7 @@ TEST(TestInputFilter, InputValidateCachedEphemeralMatch) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("usr.id"), "usr.id"); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // To validate that the cache works, we pass an object store containing @@ -566,8 +552,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // In this test we validate that when the cache is empty and only one @@ -616,8 +601,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // In this instance we pass a complete store with both addresses but an @@ -674,8 +658,7 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip"); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // In this instance we pass a complete store with both addresses but an @@ -734,8 +717,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // To validate that the cache works, we pass an object store containing @@ -798,8 +780,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // In this test we validate that when the cache is empty and only one @@ -858,8 +839,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // In this instance we pass a complete store with both addresses but an @@ -918,8 +898,7 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); // In this instance we pass a complete store with both addresses but an @@ -982,8 +961,7 @@ TEST(TestInputFilter, MatchWithDynamicMatcher) auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); input_filter filter("filter", builder.build(), {rule.get()}, std::move(obj_filter)); { diff --git a/tests/unit/exclusion/rule_filter_test.cpp b/tests/unit/exclusion/rule_filter_test.cpp index 2b713f611..6efa34e76 100644 --- a/tests/unit/exclusion/rule_filter_test.cpp +++ b/tests/unit/exclusion/rule_filter_test.cpp @@ -23,8 +23,7 @@ TEST(TestRuleFilter, Match) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; std::unordered_map addresses; @@ -59,8 +58,7 @@ TEST(TestRuleFilter, MatchWithDynamicMatcher) builder.add_target("http.client_ip"); builder.end_condition_with_data("ip_data"); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; std::unordered_map addresses; @@ -116,8 +114,7 @@ TEST(TestRuleFilter, EphemeralMatch) builder.add_target("http.client_ip"); builder.end_condition(std::vector{"192.168.0.1"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; std::unordered_map addresses; @@ -182,8 +179,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) builder.add_target("usr.id"); builder.end_condition(std::vector{"admin"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; ddwaf::exclusion::rule_filter::cache_type cache; @@ -238,8 +234,7 @@ TEST(TestRuleFilter, CachedMatchAndEphemeralMatch) builder.add_target("usr.id"); builder.end_condition(std::vector{"admin"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; ddwaf::exclusion::rule_filter::cache_type cache; @@ -296,8 +291,7 @@ TEST(TestRuleFilter, ValidateEphemeralMatchCache) builder.add_target("usr.id"); builder.end_condition(std::vector{"admin"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; ddwaf::exclusion::rule_filter::cache_type cache; @@ -349,8 +343,7 @@ TEST(TestRuleFilter, MatchWithoutCache) builder.add_target("usr.id"); builder.end_condition(std::vector{"admin"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; // In this instance we pass a complete store with both addresses but an @@ -398,8 +391,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) builder.add_target("usr.id"); builder.end_condition(std::vector{"admin"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; // In this test we validate that when the cache is empty and only one @@ -447,8 +439,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) builder.add_target("usr.id"); builder.end_condition(std::vector{"admin"}); - auto rule = - std::make_shared(ddwaf::rule("", "", {}, std::make_shared())); + auto rule = std::make_shared(core_rule("", "", {}, std::make_shared())); ddwaf::exclusion::rule_filter filter{"filter", builder.build(), {rule.get()}}; ddwaf::object_store store; diff --git a/tests/unit/mkmap_test.cpp b/tests/unit/mkmap_test.cpp index 94f8cfbd1..950987a03 100644 --- a/tests/unit/mkmap_test.cpp +++ b/tests/unit/mkmap_test.cpp @@ -12,7 +12,7 @@ using namespace ddwaf; using namespace std::literals; -using rule_tag_map = ddwaf::multi_key_map; +using rule_tag_map = ddwaf::multi_key_map; namespace { @@ -36,13 +36,13 @@ TEST(TestMultiKeyMap, Find) {"id6", "type1", "category1", {{"key", "value0"}}}, {"id7", "type1", "category1", {{"key", "value1"}}}}; - std::vector> rules; + std::vector> rules; for (const auto &spec : specs) { std::unordered_map tags = spec.tags; tags.emplace("type", spec.type); tags.emplace("category", spec.category); - auto rule_ptr = std::make_shared( + auto rule_ptr = std::make_shared( std::string(spec.id), "name", decltype(tags)(tags), std::make_shared()); rules.emplace_back(rule_ptr); ruledb.insert(rule_ptr->get_tags(), rule_ptr.get()); @@ -101,13 +101,13 @@ TEST(TestMultiKeyMap, Multifind) {"id6", "type1", "category1", {{"key", "value0"}}}, {"id7", "type1", "category1", {{"key", "value1"}}}}; - std::vector> rules; + std::vector> rules; for (const auto &spec : specs) { std::unordered_map tags = spec.tags; tags.emplace("type", spec.type); tags.emplace("category", spec.category); - auto rule_ptr = std::make_shared( + auto rule_ptr = std::make_shared( std::string(spec.id), "name", decltype(tags)(tags), std::make_shared()); rules.emplace_back(rule_ptr); ruledb.insert(rule_ptr->get_tags(), rule_ptr.get()); @@ -171,13 +171,13 @@ TEST(TestMultiKeyMap, Erase) {"id6", "type1", "category1", {{"key", "value0"}}}, {"id7", "type1", "category1", {{"key", "value1"}}}}; - std::vector> rules; + std::vector> rules; for (const auto &spec : specs) { std::unordered_map tags = spec.tags; tags.emplace("type", spec.type); tags.emplace("category", spec.category); - auto rule_ptr = std::make_shared( + auto rule_ptr = std::make_shared( std::string(spec.id), "name", decltype(tags)(tags), std::make_shared()); rules.emplace_back(rule_ptr); ruledb.insert(rule_ptr->get_tags(), rule_ptr.get()); diff --git a/tests/unit/module_test.cpp b/tests/unit/module_test.cpp new file mode 100644 index 000000000..4b1771d26 --- /dev/null +++ b/tests/unit/module_test.cpp @@ -0,0 +1,1814 @@ +// 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 "clock.hpp" +#include "common/gtest_utils.hpp" +#include "condition/scalar_condition.hpp" +#include "matcher/exact_match.hpp" +#include "matcher/ip_match.hpp" +#include "module.hpp" + +using namespace ddwaf; +using namespace std::literals; + +namespace { + +bool contains(auto events, auto id) +{ + for (const auto &event : events) { + if (event.rule->get_id() == id) { + return true; + } + } + return false; +} + +TEST(TestModuleUngrouped, SingleRuleMatch) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + + rule_module_builder mod_builder{base_rule_precedence, null_grouping_key}; + mod_builder.insert(rule.get()); + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 1); + } + + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + std::vector events; + ddwaf::timer deadline = endless_timer(); + mod.eval(events, store, cache, {}, {}, deadline); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::none); + EXPECT_EQ(events.size(), 0); + } +} + +TEST(TestModuleUngrouped, MultipleMonitoringRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + rule_module_builder mod_builder{base_rule_precedence, null_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 2); + EXPECT_TRUE(contains(events, "id1")); + EXPECT_TRUE(contains(events, "id2")); + } + + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + std::vector events; + ddwaf::timer deadline = endless_timer(); + mod.eval(events, store, cache, {}, {}, deadline); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::none); + EXPECT_EQ(events.size(), 0); + } +} + +TEST(TestModuleUngrouped, BlockingRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{base_rule_precedence, null_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } + + // No further calls should happen after a blocking rule matches +} + +TEST(TestModuleUngrouped, MonitoringRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.2"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{base_rule_precedence, null_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } + + // Check that we can still match the blocking rule + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.2")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + } +} + +TEST(TestModuleUngrouped, BlockingRuleMatchBasePrecedence) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + rules.emplace_back( + std::make_shared("id", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{base_rule_precedence, null_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } + + // No further calls should happen after a blocking rule matches +} + +TEST(TestModuleUngrouped, BlockingRuleMatchUserPrecedence) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + rules.emplace_back( + std::make_shared("id", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{user_rule_precedence, null_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } + + // No further calls should happen after a blocking rule matches +} + +TEST(TestModuleUngrouped, NonExpiringModule) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + + rule_module_builder mod_builder{ + base_rule_precedence, null_grouping_key, rule_module::expiration_policy::non_expiring}; + mod_builder.insert(rule.get()); + + auto mod = mod_builder.build(); + EXPECT_FALSE(mod.may_expire()); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline{0s}; + mod.eval(events, store, cache, {}, {}, deadline); + + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id")); + } +} + +TEST(TestModuleUngrouped, ExpiringModule) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + + rule_module_builder mod_builder{ + base_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring}; + mod_builder.insert(rule.get()); + + auto mod = mod_builder.build(); + EXPECT_TRUE(mod.may_expire()); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline{0s}; + EXPECT_THROW(mod.eval(events, store, cache, {}, {}, deadline), ddwaf::timeout_exception); + } +} + +TEST(TestModuleUngrouped, DisabledRules) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared( + "id1", "name", std::move(tags), builder.build(), std::vector{}, false)); + } + + rule_module_builder mod_builder{user_rule_precedence, null_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::none); + } +} + +TEST(TestModuleGrouped, MultipleGroupsMonitoringRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 2); + EXPECT_TRUE(contains(events, "id1")); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, MultipleGroupsBlockingRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, SingleGroupBlockingRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, SingleGroupMonitoringRuleMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } +} + +TEST(TestModuleGrouped, UserPrecedenceSingleGroupMonitoringUserMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, BasePrecedenceSingleGroupMonitoringBaseMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } +} + +TEST(TestModuleGrouped, UserPrecedenceSingleGroupBlockingBaseMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } +} + +TEST(TestModuleGrouped, UserPrecedenceSingleGroupBlockingUserMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, BasePrecedenceSingleGroupBlockingBaseMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } +} + +TEST(TestModuleGrouped, BasePrecedenceSingleGroupBlockingUserMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build(), + std::vector{"block"}, true, core_rule::source_type::base)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, UserPrecedenceMultipleGroupsMonitoringMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 2); + EXPECT_TRUE(contains(events, "id1")); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, UserPrecedenceMultipleGroupsBlockingMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, BasePrecedenceMultipleGroupsMonitoringMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id2", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 2); + EXPECT_TRUE(contains(events, "id1")); + EXPECT_TRUE(contains(events, "id2")); + } +} + +TEST(TestModuleGrouped, BasePrecedenceMultipleGroupsBlockingMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + rule_module_builder mod_builder{base_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id1")); + } +} + +TEST(TestModuleGrouped, MultipleGroupsRulesAndMatches) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id1", "name", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.2"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.2"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id3", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id4", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.2"}); + + std::unordered_map tags{ + {"type", "type3"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id5", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.2"}); + + std::unordered_map tags{ + {"type", "type4"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id6", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type5"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id7", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type6"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id8", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + { + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.2")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 4); + EXPECT_TRUE(contains(events, "id2")); + EXPECT_TRUE(contains(events, "id3")); + EXPECT_TRUE(contains(events, "id5")); + EXPECT_TRUE(contains(events, "id6")); + } +} + +TEST(TestModuleGrouped, MultipleGroupsSingleMatchPerGroup) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id3", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id4", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type3"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id5", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + { + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::monitor); + EXPECT_EQ(events.size(), 3); + + EXPECT_TRUE(contains(events, "id1")); + EXPECT_TRUE(contains(events, "id3")); + EXPECT_TRUE(contains(events, "id5")); + } +} + +TEST(TestModuleGrouped, MultipleGroupsOnlyBlockingMatch) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id1", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back( + std::make_shared("id2", "name", std::move(tags), builder.build())); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id3", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user, + core_rule::verdict_type::block)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type2"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id4", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type3"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id5", "name", std::move(tags), + builder.build(), std::vector{}, true, core_rule::source_type::user)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + { + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::block); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id3")); + } +} + +TEST(TestModuleGrouped, DisabledRules) +{ + std::vector> rules; + { + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{ + {"type", "type1"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared( + "id1", "name", std::move(tags), builder.build(), std::vector{}, false)); + } + + rule_module_builder mod_builder{user_rule_precedence, type_grouping_key}; + for (const auto &rule : rules) { mod_builder.insert(rule.get()); } + + auto mod = mod_builder.build(); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline = endless_timer(); + auto verdict = mod.eval(events, store, cache, {}, {}, deadline); + EXPECT_EQ(verdict, rule_module::verdict_type::none); + } +} + +TEST(TestModuleGrouped, NonExpiringModule) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + + rule_module_builder mod_builder{ + base_rule_precedence, type_grouping_key, rule_module::expiration_policy::non_expiring}; + mod_builder.insert(rule.get()); + + auto mod = mod_builder.build(); + EXPECT_FALSE(mod.may_expire()); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline{0s}; + mod.eval(events, store, cache, {}, {}, deadline); + + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(contains(events, "id")); + } +} + +TEST(TestModuleGrouped, ExpiringModule) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("http.client_ip"); + builder.end_condition(std::vector{"192.168.0.1"}); + + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + auto rule = std::make_shared("id", "name", std::move(tags), builder.build()); + + rule_module_builder mod_builder{ + base_rule_precedence, type_grouping_key, rule_module::expiration_policy::expiring}; + mod_builder.insert(rule.get()); + + auto mod = mod_builder.build(); + EXPECT_TRUE(mod.may_expire()); + + rule_module_cache cache; + mod.init_cache(cache); + + ddwaf::object_store store; + { + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); + + store.insert(root); + + std::vector events; + ddwaf::timer deadline{0s}; + EXPECT_THROW(mod.eval(events, store, cache, {}, {}, deadline), ddwaf::timeout_exception); + } +} + +} // namespace diff --git a/tests/unit/processor/fingerprint_test.cpp b/tests/unit/processor/fingerprint_test.cpp index cca033538..39237474b 100644 --- a/tests/unit/processor/fingerprint_test.cpp +++ b/tests/unit/processor/fingerprint_test.cpp @@ -644,6 +644,22 @@ TEST(TestHttpHeaderFingerprint, UnknownHeaders) ddwaf_object_free(&output); } +TEST(TestHttpHeaderFingerprint, InvalidHeaderType) +{ + ddwaf_object headers; + ddwaf_object_string(&headers, "value"); + + http_header_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_INVALID); + EXPECT_EQ(attr, object_store::attribute::none); + + ddwaf_object_free(&headers); +} + TEST(TestHttpNetworkFingerprint, AllXFFHeaders) { ddwaf_object tmp; @@ -817,6 +833,22 @@ TEST(TestHttpNetworkFingerprint, HeaderPrecedence) match_frag(get_headers(9), "net-10-0000000001"); } +TEST(TestNetworkHeaderFingerprint, InvalidHeaderType) +{ + ddwaf_object headers; + ddwaf_object_string(&headers, "value"); + + http_network_fingerprint gen{"id", {}, {}, false, true}; + + ddwaf::timer deadline{2s}; + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_INVALID); + EXPECT_EQ(attr, object_store::attribute::none); + + ddwaf_object_free(&headers); +} + TEST(TestSessionFingerprint, UserOnly) { ddwaf_object cookies; diff --git a/tests/unit/rule_test.cpp b/tests/unit/rule_test.cpp index 49d789e40..ec8e7280d 100644 --- a/tests/unit/rule_test.cpp +++ b/tests/unit/rule_test.cpp @@ -25,8 +25,7 @@ TEST(TestRule, Match) builder.end_condition(std::vector{"192.168.0.1"}); std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule( - "id", "name", std::move(tags), builder.build(), {"update", "block", "passlist"}); + core_rule rule("id", "name", std::move(tags), builder.build(), {"update", "block", "passlist"}); ddwaf_object root; ddwaf_object tmp; @@ -37,7 +36,7 @@ TEST(TestRule, Match) ddwaf::timer deadline{2s}; - rule::cache_type cache; + core_rule::cache_type cache; { auto scope = store.get_eval_scope(); store.insert(root, object_store::attribute::none, nullptr); @@ -45,8 +44,8 @@ TEST(TestRule, Match) auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); - EXPECT_STREQ(event->rule->get_id().c_str(), "id"); - EXPECT_STREQ(event->rule->get_name().c_str(), "name"); + EXPECT_STREQ(event->rule->get_id().data(), "id"); + EXPECT_STREQ(event->rule->get_name().data(), "name"); EXPECT_STREQ(event->rule->get_tag("type").data(), "type"); EXPECT_STREQ(event->rule->get_tag("category").data(), "category"); std::vector expected_actions{"update", "block", "passlist"}; @@ -86,8 +85,7 @@ TEST(TestRule, EphemeralMatch) builder.end_condition(std::vector{"192.168.0.1"}); std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule( - "id", "name", std::move(tags), builder.build(), {"update", "block", "passlist"}); + core_rule rule("id", "name", std::move(tags), builder.build(), {"update", "block", "passlist"}); ddwaf::object_store store; @@ -98,7 +96,7 @@ TEST(TestRule, EphemeralMatch) ddwaf::timer deadline{2s}; - rule::cache_type cache; + core_rule::cache_type cache; { auto scope = store.get_eval_scope(); store.insert(root, object_store::attribute::ephemeral, nullptr); @@ -131,7 +129,7 @@ TEST(TestRule, NoMatch) builder.end_condition(std::vector{}); std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule("id", "name", std::move(tags), builder.build()); + core_rule rule("id", "name", std::move(tags), builder.build()); ddwaf_object root; ddwaf_object tmp; @@ -143,7 +141,7 @@ TEST(TestRule, NoMatch) ddwaf::timer deadline{2s}; - rule::cache_type cache; + core_rule::cache_type cache; auto match = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(match.has_value()); } @@ -163,8 +161,8 @@ TEST(TestRule, ValidateCachedMatch) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule("id", "name", std::move(tags), builder.build()); - ddwaf::rule::cache_type cache; + core_rule rule("id", "name", std::move(tags), builder.build()); + core_rule::cache_type cache; // To validate that the cache works, we pass an object store containing // only the latest address. This ensures that the IP condition can't be @@ -195,8 +193,8 @@ TEST(TestRule, ValidateCachedMatch) ddwaf::timer deadline{2s}; auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); - EXPECT_STREQ(event->rule->get_id().c_str(), "id"); - EXPECT_STREQ(event->rule->get_name().c_str(), "name"); + EXPECT_STREQ(event->rule->get_id().data(), "id"); + EXPECT_STREQ(event->rule->get_name().data(), "name"); EXPECT_STREQ(event->rule->get_tag("type").data(), "type"); EXPECT_STREQ(event->rule->get_tag("category").data(), "category"); EXPECT_TRUE(event->rule->get_actions().empty()); @@ -238,7 +236,7 @@ TEST(TestRule, MatchWithoutCache) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule("id", "name", std::move(tags), builder.build()); + core_rule rule("id", "name", std::move(tags), builder.build()); // In this instance we pass a complete store with both addresses but an // empty cache on every run to ensure that both conditions are matched on @@ -252,7 +250,7 @@ TEST(TestRule, MatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - ddwaf::rule::cache_type cache; + core_rule::cache_type cache; auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } @@ -265,7 +263,7 @@ TEST(TestRule, MatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - ddwaf::rule::cache_type cache; + core_rule::cache_type cache; auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); @@ -305,7 +303,7 @@ TEST(TestRule, NoMatchWithoutCache) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule("id", "name", std::move(tags), builder.build()); + core_rule rule("id", "name", std::move(tags), builder.build()); // In this test we validate that when the cache is empty and only one // address is passed, the filter doesn't match (as it should be). @@ -318,7 +316,7 @@ TEST(TestRule, NoMatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - ddwaf::rule::cache_type cache; + core_rule::cache_type cache; auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } @@ -332,7 +330,7 @@ TEST(TestRule, NoMatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - ddwaf::rule::cache_type cache; + core_rule::cache_type cache; auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } @@ -353,12 +351,12 @@ TEST(TestRule, FullCachedMatchSecondRun) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule("id", "name", std::move(tags), builder.build()); + core_rule rule("id", "name", std::move(tags), builder.build()); // In this test we validate that when a match has already occurred, the // second run for the same rule returns no events regardless of input. - ddwaf::rule::cache_type cache; + core_rule::cache_type cache; { ddwaf_object root, tmp; ddwaf_object_map(&root); @@ -398,8 +396,7 @@ TEST(TestRule, ExcludeObject) std::unordered_map tags{{"type", "type"}, {"category", "category"}}; - ddwaf::rule rule( - "id", "name", std::move(tags), builder.build(), {"update", "block", "passlist"}); + core_rule rule("id", "name", std::move(tags), builder.build(), {"update", "block", "passlist"}); ddwaf_object root, tmp; ddwaf_object_map(&root); @@ -411,7 +408,7 @@ TEST(TestRule, ExcludeObject) ddwaf::timer deadline{2s}; std::unordered_set excluded_set{&root.array[0]}; - rule::cache_type cache; + core_rule::cache_type cache; auto event = rule.match(store, cache, {excluded_set, {}}, {}, deadline); EXPECT_FALSE(event.has_value()); } diff --git a/tests/unit/ruleset_test.cpp b/tests/unit/ruleset_test.cpp index 4af95953f..f53cd79d0 100644 --- a/tests/unit/ruleset_test.cpp +++ b/tests/unit/ruleset_test.cpp @@ -11,17 +11,17 @@ using namespace ddwaf; namespace { -std::shared_ptr make_rule(std::string id, std::string name, +std::shared_ptr make_rule(std::string id, std::string name, std::unordered_map tags, std::vector actions, - rule::source_type source = rule::source_type::base) + core_rule::source_type source = core_rule::source_type::base) { - return std::make_shared(std::move(id), std::move(name), std::move(tags), + return std::make_shared(std::move(id), std::move(name), std::move(tags), std::make_shared(), std::move(actions), true, source); } TEST(TestRuleset, InsertSingleRegularBaseRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {}), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {}), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {}), @@ -32,30 +32,30 @@ TEST(TestRuleset, InsertSingleRegularBaseRules) { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + /* //EXPECT_EQ(ruleset.base/g_collections.size(), 3);*/ + /*//EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0);*/ + /*//EXPECT_EQ(ruleset.user/g_collections.size(), 0);*/ + /*//EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0);*/ } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + /* //EXPECT_EQ(ruleset.base/g_collections.size(), 3);*/ + /*//EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0);*/ + /*//EXPECT_EQ(ruleset.user/g_collections.size(), 0);*/ + /*//EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0);*/ } } TEST(TestRuleset, InsertSinglePriorityBaseRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {"block"}), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}), @@ -66,30 +66,30 @@ TEST(TestRuleset, InsertSinglePriorityBaseRules) { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 3); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + ////EXPECT_EQ(ruleset.base/g_collections.size(), 0); + ////EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); + ////EXPECT_EQ(ruleset.user/g_collections.size(), 0); + ////EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 3); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + ////EXPECT_EQ(ruleset.base/g_collections.size(), 0); + ////EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); + ////EXPECT_EQ(ruleset.user/g_collections.size(), 0); + ////EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } TEST(TestRuleset, InsertSingleMixedBaseRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {}), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {}), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}), @@ -100,241 +100,241 @@ TEST(TestRuleset, InsertSingleMixedBaseRules) { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 2); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.user/g_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 2); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.user/g_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } TEST(TestRuleset, InsertSingleRegularUserRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id3", "name", {{"type", "type2"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id4", "name", {{"type", "type2"}, {"category", "category1"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id5", "name", {{"type", "type2"}, {"category", "category1"}}, {}, - rule::source_type::user), + core_rule::source_type::user), }; { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 3); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 3); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } TEST(TestRuleset, InsertSinglePriorityUserRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id3", "name", {{"type", "type2"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id4", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id5", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), }; { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 3); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 3); } } TEST(TestRuleset, InsertSingleMixedUserRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id3", "name", {{"type", "type2"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id4", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id5", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), }; { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 3); - EXPECT_EQ(ruleset.user_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 3); - EXPECT_EQ(ruleset.user_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } } TEST(TestRuleset, InsertSingleRegularMixedRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {}, - rule::source_type::base), + core_rule::source_type::base), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {}, - rule::source_type::base), + core_rule::source_type::base), make_rule("id3", "name", {{"type", "type2"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id4", "name", {{"type", "type2"}, {"category", "category1"}}, {}, - rule::source_type::base), + core_rule::source_type::base), make_rule("id5", "name", {{"type", "type2"}, {"category", "category1"}}, {}, - rule::source_type::user), + core_rule::source_type::user), }; { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 2); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 2); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 0); - EXPECT_EQ(ruleset.user_collections.size(), 2); - EXPECT_EQ(ruleset.user_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_collections.size(), 2); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } TEST(TestRuleset, InsertSinglePriorityMixedRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {"block"}, - rule::source_type::base), + core_rule::source_type::base), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}, - rule::source_type::base), + core_rule::source_type::base), make_rule("id3", "name", {{"type", "type2"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id4", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::base), + core_rule::source_type::base), make_rule("id5", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), }; { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 3); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - EXPECT_EQ(ruleset.base_collections.size(), 0); - EXPECT_EQ(ruleset.base_priority_collections.size(), 3); - EXPECT_EQ(ruleset.user_collections.size(), 0); - EXPECT_EQ(ruleset.user_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.base/g_collections.size(), 0); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_collections.size(), 0); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } } TEST(TestRuleset, InsertSingleMixedMixedRules) { - std::vector> rules{ + std::vector> rules{ make_rule("id0", "name", {{"type", "type0"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id1", "name", {{"type", "type1"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id2", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id3", "name", {{"type", "type2"}, {"category", "category0"}}, {}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id4", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id5", "name", {{"type", "type2"}, {"category", "category1"}}, {"block"}, - rule::source_type::user), + core_rule::source_type::user), make_rule("id6", "name", {{"type", "type0"}, {"category", "category0"}}, {}), make_rule("id7", "name", {{"type", "type1"}, {"category", "category0"}}, {}), make_rule("id8", "name", {{"type", "type1"}, {"category", "category0"}}, {"block"}), @@ -345,24 +345,24 @@ TEST(TestRuleset, InsertSingleMixedMixedRules) { ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 12); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 2); - EXPECT_EQ(ruleset.user_collections.size(), 3); - EXPECT_EQ(ruleset.user_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.base/g_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.user/g_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } { ddwaf::ruleset ruleset; - ruleset.insert_rules(rules); + ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 12); - EXPECT_EQ(ruleset.base_collections.size(), 3); - EXPECT_EQ(ruleset.base_priority_collections.size(), 2); - EXPECT_EQ(ruleset.user_collections.size(), 3); - EXPECT_EQ(ruleset.user_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.base/g_collections.size(), 3); + // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); + // EXPECT_EQ(ruleset.user/g_collections.size(), 3); + // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } } diff --git a/tools/waf_runner.cpp b/tools/waf_runner.cpp index c14136494..cae97e31a 100644 --- a/tools/waf_runner.cpp +++ b/tools/waf_runner.cpp @@ -126,7 +126,7 @@ int main(int argc, char *argv[]) ddwaf_result ret; auto code = - ddwaf_run(context, &persistent, &ephemeral, &ret, std::numeric_limits::max()); + ddwaf_run(context, &persistent, &ephemeral, &ret, std::numeric_limits::max()); if (code == DDWAF_MATCH && ddwaf_object_size(&ret.events) > 0) { std::stringstream ss; YAML::Emitter out(ss); diff --git a/validator/runner.cpp b/validator/runner.cpp index f9589253c..ce53fe951 100644 --- a/validator/runner.cpp +++ b/validator/runner.cpp @@ -25,6 +25,13 @@ test_runner::test_runner(const std::string &rule_file) auto id = rule_node["id"].as(); rules_[id] = rule_node; } + + auto custom_rules_node = doc["custom_rules"]; + for (auto it = custom_rules_node.begin(); it != custom_rules_node.end(); ++it) { + YAML::Node rule_node = *it; + auto id = rule_node["id"].as(); + custom_rules_[id] = rule_node; + } } test_runner::~test_runner() { ddwaf_destroy(handle_); } @@ -161,7 +168,12 @@ void test_runner::validate(const YAML::Node &expected, const YAML::Node &obtaine seen[j] = true; found_expected = true; - auto rule = rules_[id]; + YAML::Node rule; + if (rules_.contains(id)) { + rule = rules_[id]; + } else { + rule = custom_rules_[id]; + } validate_rule(rule, obtained_rule_match["rule"]); validate_conditions(rule["conditions"], obtained_rule_match["rule_matches"]); validate_matches(expected_rule_match, obtained_rule_match["rule_matches"]); diff --git a/validator/runner.hpp b/validator/runner.hpp index adab6f6b8..b5579d9af 100644 --- a/validator/runner.hpp +++ b/validator/runner.hpp @@ -48,6 +48,7 @@ class test_runner { static constexpr unsigned timeout = 1000000; ddwaf_handle handle_; std::map rules_; + std::map custom_rules_; std::stringstream output_; std::stringstream error_; }; diff --git a/validator/tests/rules/modules/authentication-acl/001_rule1_base_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/001_rule1_base_rule_match.yaml new file mode 100644 index 000000000..227e7a972 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/001_rule1_base_rule_match.yaml @@ -0,0 +1,29 @@ +{ + name: "All rules are provided in monitoring mode", + runs: [ + { + persistent-input: { + rule1-input: admin + }, + rules: [ + { + 1: [ + { + address: rule1-input, + value: admin + } + ] + }, + { + custom-1: [ + { + address: rule1-input, + value: admin + } + ] + }, + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/002_rule1_user_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/002_rule1_user_rule_match.yaml new file mode 100644 index 000000000..31dfe6d08 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/002_rule1_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that user rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule1-input: admin + }, + rules: [ + { + custom-1: [ + { + address: custom-rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..10b8f343f --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule2-input: admin + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..af02700b2 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: admin + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..303882ba1 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule3-input: admin + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..46280d8da --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: admin + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/007_rule4_blocking_base_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/007_rule4_blocking_base_rule_match.yaml new file mode 100644 index 000000000..dc63f2e91 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/007_rule4_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule4-input: admin + }, + rules: [ + { + 4: [ + { + address: rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/008_rule4_user_rule_match.yaml b/validator/tests/rules/modules/authentication-acl/008_rule4_user_rule_match.yaml new file mode 100644 index 000000000..08e4768b9 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/008_rule4_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that user rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule4-input: admin + }, + rules: [ + { + custom-4: [ + { + address: custom-rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/authentication-acl/ruleset.yaml b/validator/tests/rules/modules/authentication-acl/ruleset.yaml new file mode 100644 index 000000000..79091b8b3 --- /dev/null +++ b/validator/tests/rules/modules/authentication-acl/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "admin" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "admin" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "admin" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "admin" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "admin" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "admin" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "admin" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: authentication-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "admin" + on_match: + - block diff --git a/validator/tests/rules/modules/business-logic/001_rule1_user_rule_match.yaml b/validator/tests/rules/modules/business-logic/001_rule1_user_rule_match.yaml new file mode 100644 index 000000000..ab9ba579b --- /dev/null +++ b/validator/tests/rules/modules/business-logic/001_rule1_user_rule_match.yaml @@ -0,0 +1,30 @@ +{ + name: "All rules are provided in monitoring mode", + runs: [ + { + persistent-input: { + rule1-input: admin + }, + rules: [ + { + custom-1: [ + { + address: rule1-input, + value: admin + } + ] + }, + { + 1: [ + { + address: rule1-input, + value: admin + } + ] + } + + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/002_rule1_base_rule_match.yaml b/validator/tests/rules/modules/business-logic/002_rule1_base_rule_match.yaml new file mode 100644 index 000000000..ca5e2f1ff --- /dev/null +++ b/validator/tests/rules/modules/business-logic/002_rule1_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule1-input: admin + }, + rules: [ + { + 1: [ + { + address: base-rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/business-logic/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..10b8f343f --- /dev/null +++ b/validator/tests/rules/modules/business-logic/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule2-input: admin + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/business-logic/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..af02700b2 --- /dev/null +++ b/validator/tests/rules/modules/business-logic/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: admin + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/business-logic/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..303882ba1 --- /dev/null +++ b/validator/tests/rules/modules/business-logic/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule3-input: admin + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/business-logic/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..46280d8da --- /dev/null +++ b/validator/tests/rules/modules/business-logic/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: admin + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/007_rule4_blocking_user_rule_match.yaml b/validator/tests/rules/modules/business-logic/007_rule4_blocking_user_rule_match.yaml new file mode 100644 index 000000000..d9105eded --- /dev/null +++ b/validator/tests/rules/modules/business-logic/007_rule4_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule4-input: admin + }, + rules: [ + { + custom-4: [ + { + address: rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/008_rule4_base_rule_match.yaml b/validator/tests/rules/modules/business-logic/008_rule4_base_rule_match.yaml new file mode 100644 index 000000000..19bed6178 --- /dev/null +++ b/validator/tests/rules/modules/business-logic/008_rule4_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule4-input: admin + }, + rules: [ + { + 4: [ + { + address: base-rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/business-logic/ruleset.yaml b/validator/tests/rules/modules/business-logic/ruleset.yaml new file mode 100644 index 000000000..0ae8ef1c4 --- /dev/null +++ b/validator/tests/rules/modules/business-logic/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "admin" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "admin" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "admin" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "admin" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "admin" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "admin" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "admin" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: business-logic + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "admin" + on_match: + - block diff --git a/validator/tests/rules/modules/configuration/001_rule1_user_rule_match.yaml b/validator/tests/rules/modules/configuration/001_rule1_user_rule_match.yaml new file mode 100644 index 000000000..ab9ba579b --- /dev/null +++ b/validator/tests/rules/modules/configuration/001_rule1_user_rule_match.yaml @@ -0,0 +1,30 @@ +{ + name: "All rules are provided in monitoring mode", + runs: [ + { + persistent-input: { + rule1-input: admin + }, + rules: [ + { + custom-1: [ + { + address: rule1-input, + value: admin + } + ] + }, + { + 1: [ + { + address: rule1-input, + value: admin + } + ] + } + + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/002_rule1_base_rule_match.yaml b/validator/tests/rules/modules/configuration/002_rule1_base_rule_match.yaml new file mode 100644 index 000000000..ca5e2f1ff --- /dev/null +++ b/validator/tests/rules/modules/configuration/002_rule1_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule1-input: admin + }, + rules: [ + { + 1: [ + { + address: base-rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/configuration/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..10b8f343f --- /dev/null +++ b/validator/tests/rules/modules/configuration/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule2-input: admin + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/configuration/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..af02700b2 --- /dev/null +++ b/validator/tests/rules/modules/configuration/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: admin + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/configuration/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..303882ba1 --- /dev/null +++ b/validator/tests/rules/modules/configuration/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule3-input: admin + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/configuration/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..46280d8da --- /dev/null +++ b/validator/tests/rules/modules/configuration/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: admin + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/007_rule4_blocking_user_rule_match.yaml b/validator/tests/rules/modules/configuration/007_rule4_blocking_user_rule_match.yaml new file mode 100644 index 000000000..d9105eded --- /dev/null +++ b/validator/tests/rules/modules/configuration/007_rule4_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule4-input: admin + }, + rules: [ + { + custom-4: [ + { + address: rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/008_rule4_base_rule_match.yaml b/validator/tests/rules/modules/configuration/008_rule4_base_rule_match.yaml new file mode 100644 index 000000000..19bed6178 --- /dev/null +++ b/validator/tests/rules/modules/configuration/008_rule4_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule4-input: admin + }, + rules: [ + { + 4: [ + { + address: base-rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/configuration/ruleset.yaml b/validator/tests/rules/modules/configuration/ruleset.yaml new file mode 100644 index 000000000..f9f022943 --- /dev/null +++ b/validator/tests/rules/modules/configuration/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "admin" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "admin" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "admin" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "admin" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "admin" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "admin" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "admin" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: configuration + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "admin" + on_match: + - block diff --git a/validator/tests/rules/modules/custom-acl/001_rule1_user_rule_match.yaml b/validator/tests/rules/modules/custom-acl/001_rule1_user_rule_match.yaml new file mode 100644 index 000000000..c5f6f7e8f --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/001_rule1_user_rule_match.yaml @@ -0,0 +1,29 @@ +{ + name: "All rules are provided in monitoring mode", + runs: [ + { + persistent-input: { + rule1-input: admin + }, + rules: [ + { + custom-1: [ + { + address: rule1-input, + value: admin + } + ] + }, + { + 1: [ + { + address: rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/002_rule1_base_rule_match.yaml b/validator/tests/rules/modules/custom-acl/002_rule1_base_rule_match.yaml new file mode 100644 index 000000000..ca5e2f1ff --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/002_rule1_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule1-input: admin + }, + rules: [ + { + 1: [ + { + address: base-rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/custom-acl/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..10b8f343f --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule2-input: admin + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/custom-acl/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..af02700b2 --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: admin + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/custom-acl/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..303882ba1 --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule3-input: admin + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/custom-acl/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..46280d8da --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: admin + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/007_rule4_blocking_user_rule_match.yaml b/validator/tests/rules/modules/custom-acl/007_rule4_blocking_user_rule_match.yaml new file mode 100644 index 000000000..d9105eded --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/007_rule4_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule4-input: admin + }, + rules: [ + { + custom-4: [ + { + address: rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/008_rule4_base_rule_match.yaml b/validator/tests/rules/modules/custom-acl/008_rule4_base_rule_match.yaml new file mode 100644 index 000000000..19bed6178 --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/008_rule4_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule4-input: admin + }, + rules: [ + { + 4: [ + { + address: base-rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/custom-acl/ruleset.yaml b/validator/tests/rules/modules/custom-acl/ruleset.yaml new file mode 100644 index 000000000..eea89dc8f --- /dev/null +++ b/validator/tests/rules/modules/custom-acl/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "admin" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "admin" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "admin" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "admin" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "admin" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "admin" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "admin" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: custom-acl + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "admin" + on_match: + - block diff --git a/validator/tests/rules/modules/network-acl/001_rule1_base_rule_match.yaml b/validator/tests/rules/modules/network-acl/001_rule1_base_rule_match.yaml new file mode 100644 index 000000000..af567d00c --- /dev/null +++ b/validator/tests/rules/modules/network-acl/001_rule1_base_rule_match.yaml @@ -0,0 +1,29 @@ +{ + name: "All rules are provided in monitoring mode", + runs: [ + { + persistent-input: { + rule1-input: 192.168.0.1 + }, + rules: [ + { + 1: [ + { + address: rule1-input, + value: 192.168.0.1 + } + ], + }, + { + custom-1: [ + { + address: rule1-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/002_rule1_user_rule_match.yaml b/validator/tests/rules/modules/network-acl/002_rule1_user_rule_match.yaml new file mode 100644 index 000000000..2d8673189 --- /dev/null +++ b/validator/tests/rules/modules/network-acl/002_rule1_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that user rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule1-input: 192.168.0.1 + }, + rules: [ + { + custom-1: [ + { + address: custom-rule1-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/network-acl/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..a2a6ca970 --- /dev/null +++ b/validator/tests/rules/modules/network-acl/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in network-acl", + runs: [ + { + persistent-input: { + rule2-input: 192.168.0.1 + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/network-acl/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..0dffe3b30 --- /dev/null +++ b/validator/tests/rules/modules/network-acl/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: 192.168.0.1 + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/network-acl/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..9aca8f149 --- /dev/null +++ b/validator/tests/rules/modules/network-acl/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in network-acl", + runs: [ + { + persistent-input: { + rule3-input: 192.168.0.1 + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/network-acl/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..6391389af --- /dev/null +++ b/validator/tests/rules/modules/network-acl/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: 192.168.0.1 + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/007_rule4_blocking_base_rule_match.yaml b/validator/tests/rules/modules/network-acl/007_rule4_blocking_base_rule_match.yaml new file mode 100644 index 000000000..1f97f3cdb --- /dev/null +++ b/validator/tests/rules/modules/network-acl/007_rule4_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over blocking user rules in network-acl", + runs: [ + { + persistent-input: { + rule4-input: 192.168.0.1 + }, + rules: [ + { + 4: [ + { + address: rule4-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/008_rule4_user_rule_match.yaml b/validator/tests/rules/modules/network-acl/008_rule4_user_rule_match.yaml new file mode 100644 index 000000000..49c4a25ab --- /dev/null +++ b/validator/tests/rules/modules/network-acl/008_rule4_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that user rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule4-input: 192.168.0.1 + }, + rules: [ + { + custom-4: [ + { + address: custom-rule4-input, + value: 192.168.0.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/network-acl/ruleset.yaml b/validator/tests/rules/modules/network-acl/ruleset.yaml new file mode 100644 index 000000000..77ef22c71 --- /dev/null +++ b/validator/tests/rules/modules/network-acl/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "192.168.0.1" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "192.168.0.1" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "192.168.0.1" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "192.168.0.1" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "192.168.0.1" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "192.168.0.1" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "192.168.0.1" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: network-acl + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "192.168.0.1" + on_match: + - block diff --git a/validator/tests/rules/modules/rasp/001_rule1_base_rule_match.yaml b/validator/tests/rules/modules/rasp/001_rule1_base_rule_match.yaml new file mode 100644 index 000000000..7d4d3ef92 --- /dev/null +++ b/validator/tests/rules/modules/rasp/001_rule1_base_rule_match.yaml @@ -0,0 +1,29 @@ +{ + name: "All rules are provided in monitoring mode", + runs: [ + { + persistent-input: { + rule1-input: admin + }, + rules: [ + { + 1: [ + { + address: rule1-input, + value: admin + } + ] + }, + { + custom-1: [ + { + address: rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/002_rule1_user_rule_match.yaml b/validator/tests/rules/modules/rasp/002_rule1_user_rule_match.yaml new file mode 100644 index 000000000..31dfe6d08 --- /dev/null +++ b/validator/tests/rules/modules/rasp/002_rule1_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that user rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule1-input: admin + }, + rules: [ + { + custom-1: [ + { + address: custom-rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/rasp/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..10b8f343f --- /dev/null +++ b/validator/tests/rules/modules/rasp/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule2-input: admin + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/rasp/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..af02700b2 --- /dev/null +++ b/validator/tests/rules/modules/rasp/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: admin + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/rasp/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..303882ba1 --- /dev/null +++ b/validator/tests/rules/modules/rasp/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule3-input: admin + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/rasp/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..46280d8da --- /dev/null +++ b/validator/tests/rules/modules/rasp/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: admin + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/007_rule4_blocking_base_rule_match.yaml b/validator/tests/rules/modules/rasp/007_rule4_blocking_base_rule_match.yaml new file mode 100644 index 000000000..dc63f2e91 --- /dev/null +++ b/validator/tests/rules/modules/rasp/007_rule4_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule4-input: admin + }, + rules: [ + { + 4: [ + { + address: rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/008_rule4_user_rule_match.yaml b/validator/tests/rules/modules/rasp/008_rule4_user_rule_match.yaml new file mode 100644 index 000000000..08e4768b9 --- /dev/null +++ b/validator/tests/rules/modules/rasp/008_rule4_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that user rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule4-input: admin + }, + rules: [ + { + custom-4: [ + { + address: custom-rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/rasp/ruleset.yaml b/validator/tests/rules/modules/rasp/ruleset.yaml new file mode 100644 index 000000000..6761ff8bc --- /dev/null +++ b/validator/tests/rules/modules/rasp/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "admin" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "admin" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "admin" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "admin" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "admin" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "admin" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "admin" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: rasp + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "admin" + on_match: + - block diff --git a/validator/tests/rules/modules/waf/001_rule1_user_rule_match.yaml b/validator/tests/rules/modules/waf/001_rule1_user_rule_match.yaml new file mode 100644 index 000000000..f0a0d6bc7 --- /dev/null +++ b/validator/tests/rules/modules/waf/001_rule1_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "User rules have precedence in custom-acl", + runs: [ + { + persistent-input: { + rule1-input: admin + }, + rules: [ + { + custom-1: [ + { + address: rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/002_rule1_base_rule_match.yaml b/validator/tests/rules/modules/waf/002_rule1_base_rule_match.yaml new file mode 100644 index 000000000..ca5e2f1ff --- /dev/null +++ b/validator/tests/rules/modules/waf/002_rule1_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule1-input: admin + }, + rules: [ + { + 1: [ + { + address: base-rule1-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/003_rule2_blocking_user_rule_match.yaml b/validator/tests/rules/modules/waf/003_rule2_blocking_user_rule_match.yaml new file mode 100644 index 000000000..10b8f343f --- /dev/null +++ b/validator/tests/rules/modules/waf/003_rule2_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over non-blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule2-input: admin + }, + rules: [ + { + custom-2: [ + { + address: rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/004_rule2_base_rule_match.yaml b/validator/tests/rules/modules/waf/004_rule2_base_rule_match.yaml new file mode 100644 index 000000000..af02700b2 --- /dev/null +++ b/validator/tests/rules/modules/waf/004_rule2_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule2-input: admin + }, + rules: [ + { + 2: [ + { + address: base-rule2-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/005_rule3_blocking_base_rule_match.yaml b/validator/tests/rules/modules/waf/005_rule3_blocking_base_rule_match.yaml new file mode 100644 index 000000000..303882ba1 --- /dev/null +++ b/validator/tests/rules/modules/waf/005_rule3_blocking_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking base rules have precedence over non-blocking user rules in authentication-acl", + runs: [ + { + persistent-input: { + rule3-input: admin + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/006_rule3_user_rule_match.yaml b/validator/tests/rules/modules/waf/006_rule3_user_rule_match.yaml new file mode 100644 index 000000000..46280d8da --- /dev/null +++ b/validator/tests/rules/modules/waf/006_rule3_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + custom-rule3-input: admin + }, + rules: [ + { + custom-3: [ + { + address: custom-rule3-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/007_rule4_blocking_user_rule_match.yaml b/validator/tests/rules/modules/waf/007_rule4_blocking_user_rule_match.yaml new file mode 100644 index 000000000..d9105eded --- /dev/null +++ b/validator/tests/rules/modules/waf/007_rule4_blocking_user_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Blocking user rules have precedence over blocking base rules in authentication-acl", + runs: [ + { + persistent-input: { + rule4-input: admin + }, + rules: [ + { + custom-4: [ + { + address: rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/008_rule4_base_rule_match.yaml b/validator/tests/rules/modules/waf/008_rule4_base_rule_match.yaml new file mode 100644 index 000000000..19bed6178 --- /dev/null +++ b/validator/tests/rules/modules/waf/008_rule4_base_rule_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Validate that base rules still match when there's no contention", + runs: [ + { + persistent-input: { + base-rule4-input: admin + }, + rules: [ + { + 4: [ + { + address: base-rule4-input, + value: admin + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/modules/waf/ruleset.yaml b/validator/tests/rules/modules/waf/ruleset.yaml new file mode 100644 index 000000000..f6959777f --- /dev/null +++ b/validator/tests/rules/modules/waf/ruleset.yaml @@ -0,0 +1,123 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-non-blocking + tags: + type: flow1 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: base-rule1-input + list: + - "admin" + - id: "2" + name: rule2-non-blocking + tags: + type: flow2 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: base-rule2-input + list: + - "admin" + - id: "3" + name: rule3-blocking + tags: + type: flow3 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: base-rule3-input + list: + - "admin" + on_match: + - block + - id: "4" + name: rule4-blocking + tags: + type: flow4 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: base-rule4-input + list: + - "admin" + on_match: + - block +custom_rules: + - id: "custom-1" + name: custom-rule1-non-blocking + tags: + type: flow1 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule1-input + - address: custom-rule1-input + list: + - "admin" + - id: "custom-2" + name: custom-rule2-blocking + tags: + type: flow2 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule2-input + - address: custom-rule2-input + list: + - "admin" + on_match: + - block + - id: "custom-3" + name: custom-rule3-blocking + tags: + type: flow3 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule3-input + - address: custom-rule3-input + list: + - "admin" + - id: "custom-4" + name: custom-rule4-blocking + tags: + type: flow4 + category: category + module: waf + conditions: + - operator: exact_match + parameters: + inputs: + - address: rule4-input + - address: custom-rule4-input + list: + - "admin" + on_match: + - block