From 1b3a53d9dfe9848d6e6304b8934b09b12e4cff7a Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:21:02 +0000 Subject: [PATCH] Add more tests, improvements, etc --- src/builder/module_builder.hpp | 32 ++++---- src/context.cpp | 10 +-- src/module.cpp | 19 ++++- src/module.hpp | 2 + tests/unit/context_test.cpp | 95 ---------------------- tests/unit/module_test.cpp | 144 +++++++++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 124 deletions(-) create mode 100644 tests/unit/module_test.cpp diff --git a/src/builder/module_builder.hpp b/src/builder/module_builder.hpp index d26c7006..8f76d697 100644 --- a/src/builder/module_builder.hpp +++ b/src/builder/module_builder.hpp @@ -12,6 +12,22 @@ 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; +} + +inline std::string_view type_grouping_key(const core_rule *rule) { return rule->get_type(); } +inline 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: @@ -55,22 +71,6 @@ class rule_module_set_builder { const std::vector> &rules); protected: - // Helpers - static bool user_rule_precedence( - const core_rule::source_type left, const core_rule::source_type right) - { - return left > right; - } - - static bool base_rule_precedence( - const core_rule::source_type left, const core_rule::source_type right) - { - return left < right; - } - - static std::string_view type_grouping_key(const core_rule *rule) { return rule->get_type(); } - static constexpr std::string_view null_grouping_key(const core_rule * /*rule*/) { return {}; } - std::array builders_{{ // Network-ACL {base_rule_precedence, null_grouping_key, rule_module::expiration_policy::non_expiring}, diff --git a/src/context.cpp b/src/context.cpp index 0f9da9c6..773d9b20 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -225,21 +225,13 @@ exclusion::context_policy &context::eval_filters(ddwaf::timer &deadline) std::vector context::eval_rules( const exclusion::context_policy &policy, ddwaf::timer &deadline) { - static auto no_deadline = endless_timer(); - std::vector events; 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]; - rule_module::verdict_type verdict = rule_module::verdict_type::none; - if (mod.may_expire()) { - verdict = mod.eval(events, store_, cache, policy, ruleset_->rule_matchers, deadline); - } else { - verdict = mod.eval(events, store_, cache, policy, ruleset_->rule_matchers, no_deadline); - } - + auto verdict = mod.eval(events, store_, cache, policy, ruleset_->rule_matchers, deadline); if (verdict == rule_module::verdict_type::block) { break; } diff --git a/src/module.cpp b/src/module.cpp index e770e46d..604b1b6d 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -85,6 +85,12 @@ std::pair, verdict_type> eval_rule(const core_rule &rule, } // 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, @@ -132,22 +138,27 @@ verdict_type rule_module::eval(std::vector &events, object_store &store, 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, deadline); + 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()); - return verdict; + if (verdict > final_verdict) { + final_verdict = verdict; + } } } - return verdict_type::none; + return final_verdict; } - return eval_with_collections(events, store, cache, exclusion, dynamic_matchers, deadline); + return eval_with_collections(events, store, cache, exclusion, dynamic_matchers, apt_deadline); } } // namespace ddwaf diff --git a/src/module.hpp b/src/module.hpp index b632fa1e..4cf22b5d 100644 --- a/src/module.hpp +++ b/src/module.hpp @@ -62,6 +62,8 @@ class rule_module { 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; diff --git a/tests/unit/context_test.cpp b/tests/unit/context_test.cpp index bc8ed97b..53fa30c1 100644 --- a/tests/unit/context_test.cpp +++ b/tests/unit/context_test.cpp @@ -679,101 +679,6 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) } } -// TODO: collections don't work like that any longer -/*TEST(TestContext, MatchMultipleRulesWithPriorityUntilAllActionsMet)*/ -/*{*/ -/*auto ruleset = test::get_default_ruleset();*/ -/*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", "category1"}};*/ - -/*rules.emplace_back(*/ -/*std::make_shared("id1", "name1", std::move(tags), builder.build()));*/ -/*}*/ - -/*{*/ -/*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"}};*/ - -/*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);*/ - -/*{*/ -/*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().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");*/ -/*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().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{"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(); diff --git a/tests/unit/module_test.cpp b/tests/unit/module_test.cpp new file mode 100644 index 00000000..07db6db0 --- /dev/null +++ b/tests/unit/module_test.cpp @@ -0,0 +1,144 @@ +// 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 { + +TEST(TestModule, 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(); + mod.eval(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 = endless_timer(); + mod.eval(events, store, cache, {}, {}, deadline); + + EXPECT_EQ(events.size(), 0); + } +} + +TEST(TestModule, 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(); + + 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); + } +} + +TEST(TestModule, 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(); + + 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