From fe0bd8389ba1dffeb0579363f09d43a5dc803fa2 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:25:22 +0000 Subject: [PATCH] WAF Ruleset Builder & Release v1.8.0 (#138) --- CHANGELOG.md | 10 + CMakeLists.txt | 2 +- UPGRADING.md | 65 + include/ddwaf.h | 53 +- libddwaf.def | 4 +- src/collection.cpp | 27 +- src/collection.hpp | 6 +- src/condition.cpp | 54 +- src/condition.hpp | 51 +- src/config.hpp | 6 - src/context.cpp | 21 +- src/context.hpp | 16 +- src/exclusion/input_filter.cpp | 14 +- src/exclusion/input_filter.hpp | 14 +- src/exclusion/object_filter.hpp | 4 + src/exclusion/rule_filter.cpp | 9 +- src/exclusion/rule_filter.hpp | 9 +- src/interface.cpp | 137 +- src/manifest.cpp | 48 +- src/manifest.hpp | 114 +- src/mkmap.hpp | 125 ++ src/object_store.cpp | 7 +- src/object_store.hpp | 2 +- src/parameter.cpp | 20 +- src/parameter.hpp | 26 +- src/parser/common.hpp | 10 +- src/parser/parser.cpp | 34 +- src/parser/parser.hpp | 23 +- src/parser/parser_v1.cpp | 73 +- src/parser/parser_v2.cpp | 556 +++--- src/parser/rule_data_parser.cpp | 5 +- src/parser/specification.hpp | 84 + src/rule.cpp | 28 +- src/rule.hpp | 42 +- src/rule_data_dispatcher.hpp | 138 -- src/rule_processor/base.hpp | 2 +- src/ruleset.hpp | 70 +- src/ruleset_builder.cpp | 291 ++++ src/ruleset_builder.hpp | 105 ++ src/type_traits.hpp | 17 + src/waf.cpp | 100 -- src/waf.hpp | 61 +- tests/CMakeLists.txt | 5 +- tests/TestAdditive.cpp | 4 +- tests/TestInterface.cpp | 2 +- tests/collection_test.cpp | 154 +- tests/condition_test.cpp | 53 +- tests/context_test.cpp | 942 ++++++----- tests/input_filter_test.cpp | 343 ++-- tests/interface_test.cpp | 1498 ++++++++++++++++- tests/main.cpp | 2 +- tests/manifest_test.cpp | 220 +-- tests/mkmap_test.cpp | 138 ++ tests/object_filter_test.cpp | 74 +- tests/object_store_test.cpp | 131 +- tests/parameter_test.cpp | 38 +- tests/parser_test.cpp | 452 ----- tests/parser_v1_test.cpp | 230 +++ tests/parser_v2_input_filters.cpp | 67 + tests/parser_v2_interface_test.cpp | 256 +++ tests/parser_v2_rule_filters.cpp | 316 ++++ tests/parser_v2_rules_data_test.cpp | 126 ++ tests/parser_v2_rules_override_test.cpp | 86 + tests/parser_v2_rules_test.cpp | 188 +++ tests/rule_data_dispatcher_test.cpp | 779 --------- tests/rule_filter_test.cpp | 88 +- tests/rule_test.cpp | 336 +--- tests/ruleset_test.cpp | 131 +- tests/test.h | 10 +- tests/waf_test.cpp | 154 +- tests/yaml/interface.yaml | 2 + tests/yaml/interface3.yaml | 14 + tests/yaml/interface_with_data.yaml | 50 + tests/yaml/rule_data.yaml | 2 + .../libinjection/src/libinjection_xss.c | 2 - validator/ruleset.yaml | 17 + version | 2 +- 77 files changed, 5619 insertions(+), 3776 deletions(-) create mode 100644 src/mkmap.hpp create mode 100644 src/parser/specification.hpp delete mode 100644 src/rule_data_dispatcher.hpp create mode 100644 src/ruleset_builder.cpp create mode 100644 src/ruleset_builder.hpp create mode 100644 src/type_traits.hpp delete mode 100644 src/waf.cpp create mode 100644 tests/mkmap_test.cpp delete mode 100644 tests/parser_test.cpp create mode 100644 tests/parser_v1_test.cpp create mode 100644 tests/parser_v2_input_filters.cpp create mode 100644 tests/parser_v2_interface_test.cpp create mode 100644 tests/parser_v2_rule_filters.cpp create mode 100644 tests/parser_v2_rules_data_test.cpp create mode 100644 tests/parser_v2_rules_override_test.cpp create mode 100644 tests/parser_v2_rules_test.cpp delete mode 100644 tests/rule_data_dispatcher_test.cpp create mode 100644 tests/yaml/interface3.yaml create mode 100644 tests/yaml/interface_with_data.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index fb9ffa53a..ed0dab5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # libddwaf release +### v1.8.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) +### API \& Breaking Changes +- Add `ddwaf_update` for all-in-one ruleset updates ([#138](https://github.com/DataDog/libddwaf/pull/138)) +- Remove `ddwaf_required_rule_data_ids` ([#138](https://github.com/DataDog/libddwaf/pull/138)) +- Remove `ddwaf_update_rule_data` ([#138](https://github.com/DataDog/libddwaf/pull/138)) +- Remove `ddwaf_toggle_rules` ([#138](https://github.com/DataDog/libddwaf/pull/138)) + +### Changes +- Add WAF Builder ([#138](https://github.com/DataDog/libddwaf/pull/138)) + ### v1.7.0 ([unstable](https://github.com/DataDog/libddwaf/blob/master/README.md#versioning-semantics)) - 2023/02/06 #### Changes - Handle lifetime extension ([#135](https://github.com/DataDog/libddwaf/pull/135)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15102f935..351f4c76d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,9 +62,9 @@ include(GNUInstallDirs) add_subdirectory(third_party) set(LIBDDWAF_SOURCE + ${libddwaf_SOURCE_DIR}/src/ruleset_builder.cpp ${libddwaf_SOURCE_DIR}/src/clock.cpp ${libddwaf_SOURCE_DIR}/src/parameter.cpp - ${libddwaf_SOURCE_DIR}/src/waf.cpp ${libddwaf_SOURCE_DIR}/src/interface.cpp ${libddwaf_SOURCE_DIR}/src/context.cpp ${libddwaf_SOURCE_DIR}/src/event.cpp diff --git a/UPGRADING.md b/UPGRADING.md index 741f3b044..22b4c2432 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,70 @@ # Upgrading libddwaf +### Upgrading from `1.7.x` to `1.8.0` + +Version `1.8.0` introduces the WAF builder, a new module with the ability to generate a new WAF instance from an existing one. This new module works transparently through the `ddwaf_update` function, which allows the user to update one, some or all of the following: +- The complete ruleset through the `rules` key. +- The `on_match` or `enabled` field of specific rules through the `rules_override` key. +- Exclusion filters through the `exclusions` key. +- Rule data through the `rules_data` key. + +The WAF builder has a number of objectives: +- Provide a mechanism to generate and update the WAF as needed. +- Remove all existing mutexes. +- Remove all side-effects on running contexts. +- __Potentially__ provide efficiency gains: + - Avoiding the need to parse a whole ruleset on every update. + - Reusing internal structures, objects and containers whenever possible. + +With the introduction of `ddwaf_update`, the following functions have been deprecated and removed: +- `ddwaf_toggle_rules` +- `ddwaf_update_rule_data` +- `ddwaf_required_rule_data_ids` + +The first two functions have been removed due to the added complexity of supporting multiple interfaces with a similar outcome but different inputs. On the other hand, the last function was simply removed in favour of letting the WAF handle unexpected rule data IDs more gracefully, however this function can be reintroduced later if deemed necessary. + +Typically, the new interface will be used as follows on all instances: + +```c + ddwaf_handle old_handle = ddwaf_init(&ruleset, &config, &info); + ddwaf_object_free(&ruleset); + + ddwaf_handle new_handle = ddwaf_update(old_handle, &update, &new_info); + ddwaf_object_free(&update); + if (new_handle != NULL) { + ddwaf_destroy(old_handle); + } +``` + +The `ddwaf_update` function returns a new `ddwaf_handle` which will be a valid pointer if the update succeeded, or `NULL` if there was nothing to update or there was an error. Creating contexts while calling `ddwaf_update` is, in theory, perfectly legal as well as destroying a handle while associated contexts are still in use, for example: + +```c + ddwaf_handle old_handle = ddwaf_init(&ruleset, &config, &info); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(old_handle); + + ddwaf_handle new_handle = ddwaf_update(old_handle, &new_ruleset, &new_info); + ddwaf_object_free(&new_rule); + if (new_handle != NULL) { + // Destroying the handle should not invalidate the context + ddwaf_destroy(old_handle); + } + + // Both the context and the handle are destroyed here + ddwaf_context_destroy(context); +``` +Note that the `ddwaf_update` function also has an optional input parameter for the `ruleset_info` structure, this will only provide useful diagnostics when the update provided contains new rules (within the `rules` key), also note that the `ruleset_info` should either be a fresh new structure or the previously used after calling `ddwaf_ruleset_info_free`. + +Finally, you can call `ddwaf_init` with all previously mentioned keys, or a combination of them, however the `rules` key is mandatory. This does not apply to `ddwaf_update. + +#### Notes on thread-safety + +The thread-safety of any operations on the handle depends on whether they act on the ruleset or the builder itself, generally: +- Calling `ddwaf_update` concurrently, regardless of the handle, is never thread-safe. +- Calling `ddwaf_context_init` concurrently on the same handle is thread-safe. +- Calling `ddwaf_context_init` and `ddwaf_update` concurrently on the same handle is also thread-safe. + ### Upgrading from `1.6.x` to `1.7.0` There are no API changes in 1.7.0, however `ddwaf_handle` is now reference-counted and shared between the user and each `ddwaf_context`. This means that it is now possible to call `ddwaf_destroy` on a `ddwaf_handle` without invalidating any `ddwaf_context` in use instantiated from said `ddwaf_handle`. For example, the following snippet is now perfectly legal and will work as expected (note that any checks have been omitted for brevity): diff --git a/include/ddwaf.h b/include/ddwaf.h index 63636c660..9cbab964e 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -8,12 +8,11 @@ #define DDWAF_H #ifdef __cplusplus -#include namespace ddwaf{ class waf; class context; } // namespace ddwaf -using ddwaf_handle = std::shared_ptr *; +using ddwaf_handle = ddwaf::waf *; using ddwaf_context = ddwaf::context *; extern "C" @@ -207,46 +206,40 @@ typedef void (*ddwaf_log_cb)( * * Initialize a ddwaf instance * - * @param rule ddwaf::object containing the patterns to be used by the WAF. (nonnull) + * @param rule ddwaf::object map containing rules, exclusions, rules_override and rules_data. (nonnull) * @param config Optional configuration of the WAF. (nullable) * @param info Optional ruleset parsing diagnostics. (nullable) * - * @return Handle to the WAF instance. + * @return Handle to the WAF instance or NULL on error. * * @note If config is NULL, default values will be used, including the default * free function (ddwaf_object_free). **/ -ddwaf_handle ddwaf_init(const ddwaf_object *rule, +ddwaf_handle ddwaf_init(const ddwaf_object *ruleset, const ddwaf_config* config, ddwaf_ruleset_info *info); /** - * ddwaf_destroy - * - * Destroy a WAF instance. + * ddwaf_update * - * @param Handle to the WAF instance. - */ -void ddwaf_destroy(ddwaf_handle handle); - -/** - * ddwaf_update_rule_data + * Update a ddwaf instance * - * Update existing rules with new rule data. + * @param rule ddwaf::object map containing rules, exclusions, rules_override and rules_data. (nonnull) + * @param info Optional ruleset parsing diagnostics. (nullable) * - * @param handle to the WAF instance. - * @param data A ddwaf_object with the format [{id, type, [data]}]. - */ -DDWAF_RET_CODE ddwaf_update_rule_data(ddwaf_handle handle, ddwaf_object *data); + * @return Handle to the new WAF instance or NULL if there were no new updates + * or there was an error processing the ruleset. + **/ +ddwaf_handle ddwaf_update(ddwaf_handle handle, const ddwaf_object *ruleset, + ddwaf_ruleset_info *info); /** - * ddwaf_toggle_rules + * ddwaf_destroy * - * Enable or disable rules (true -> rule enabled, false -> rule disabled). + * Destroy a WAF instance. * - * @param handle to the WAF instance. - * @param data A ddwaf_object with the format {rule_id : boolean}. + * @param Handle to the WAF instance. */ -DDWAF_RET_CODE ddwaf_toggle_rules(ddwaf_handle handle, ddwaf_object *rule_map); +void ddwaf_destroy(ddwaf_handle handle); /** * ddwaf_ruleset_info_free @@ -268,18 +261,6 @@ void ddwaf_ruleset_info_free(ddwaf_ruleset_info *info); * @return NULL if empty, otherwise a pointer to an array with size elements. **/ const char* const* ddwaf_required_addresses(const ddwaf_handle handle, uint32_t *size); -/** - * ddwaf_required_rule_data_ids - * - * Get a list of required rule data IDs (if any). The memory is owned by the - * WAF and should not be freed. - * - * @param Handle to the WAF instance. - * @param size Output parameter in which the size will be returned. The value of - * size will be 0 if the return value is NULL. - * @return NULL if empty, otherwise a pointer to an array with size elements. - **/ -const char* const* ddwaf_required_rule_data_ids(const ddwaf_handle handle, uint32_t *size); /** * ddwaf_context_init diff --git a/libddwaf.def b/libddwaf.def index 79d1186d5..251df8c1b 100644 --- a/libddwaf.def +++ b/libddwaf.def @@ -1,12 +1,10 @@ LIBRARY ddwaf EXPORTS ddwaf_init + ddwaf_update ddwaf_destroy - ddwaf_update_rule_data - ddwaf_toggle_rules ddwaf_ruleset_info_free ddwaf_required_addresses - ddwaf_required_rule_data_ids ddwaf_context_init ddwaf_run ddwaf_context_destroy diff --git a/src/collection.cpp b/src/collection.cpp index 786897f9b..4f46cc013 100644 --- a/src/collection.cpp +++ b/src/collection.cpp @@ -12,9 +12,10 @@ namespace ddwaf { namespace { std::optional match_rule(const rule::ptr &rule, const object_store &store, - const ddwaf::manifest &manifest, std::unordered_map &cache, + std::unordered_map &cache, const std::unordered_set &rules_to_exclude, const std::unordered_map &objects_to_exclude, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) { const auto &id = rule->id; @@ -47,9 +48,9 @@ std::optional match_rule(const rule::ptr &rule, const object_store &store auto exclude_it = objects_to_exclude.find(rule); if (exclude_it != objects_to_exclude.end()) { const auto &objects_excluded = exclude_it->second; - event = rule->match(store, manifest, rule_cache, objects_excluded, deadline); + event = rule->match(store, rule_cache, objects_excluded, dynamic_processors, deadline); } else { - event = rule->match(store, manifest, rule_cache, {}, deadline); + event = rule->match(store, rule_cache, {}, dynamic_processors, deadline); } return event; @@ -64,9 +65,9 @@ std::optional match_rule(const rule::ptr &rule, const object_store &store void collection::match(std::vector &events, std::unordered_set & /*seen_actions*/, const object_store &store, - const ddwaf::manifest &manifest, collection_cache &cache, - const std::unordered_set &rules_to_exclude, + collection_cache &cache, const std::unordered_set &rules_to_exclude, const std::unordered_map &objects_to_exclude, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const { if (cache.result) { @@ -74,8 +75,8 @@ void collection::match(std::vector &events, } for (const auto &rule : rules_) { - auto event = match_rule(rule, store, manifest, cache.rule_cache, rules_to_exclude, - objects_to_exclude, deadline); + auto event = match_rule(rule, store, cache.rule_cache, rules_to_exclude, objects_to_exclude, + dynamic_processors, deadline); if (event.has_value()) { cache.result = true; events.emplace_back(std::move(*event)); @@ -87,9 +88,9 @@ void collection::match(std::vector &events, void priority_collection::match(std::vector &events, std::unordered_set &seen_actions, const object_store &store, - const ddwaf::manifest &manifest, collection_cache &cache, - const std::unordered_set &rules_to_exclude, + collection_cache &cache, const std::unordered_set &rules_to_exclude, const std::unordered_map &objects_to_exclude, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const { auto &remaining_actions = cache.remaining_actions; @@ -103,15 +104,15 @@ void priority_collection::match(std::vector &events, // If there are no remaining actions, we treat this collection as a regular one if (remaining_actions.empty()) { - collection::match(events, seen_actions, store, manifest, cache, rules_to_exclude, - objects_to_exclude, deadline); + collection::match(events, seen_actions, store, cache, rules_to_exclude, objects_to_exclude, + dynamic_processors, deadline); return; } // If there are still remaining actions, we treat this collection as a priority tone for (const auto &rule : rules_) { - auto event = match_rule(rule, store, manifest, cache.rule_cache, rules_to_exclude, - objects_to_exclude, deadline); + auto event = match_rule(rule, store, cache.rule_cache, rules_to_exclude, objects_to_exclude, + dynamic_processors, deadline); if (event.has_value()) { // If there has been a match, we set the result to true to ensure // that the equivalent regular collection doesn't attempt to match diff --git a/src/collection.hpp b/src/collection.hpp index e29c5d04a..c29d0cdf2 100644 --- a/src/collection.hpp +++ b/src/collection.hpp @@ -51,9 +51,10 @@ class collection { virtual void match(std::vector &events /* output */, std::unordered_set &seen_actions /* input & output */, - const object_store &store, const ddwaf::manifest &manifest, collection_cache &cache, + const object_store &store, collection_cache &cache, const std::unordered_set &rules_to_exclude, const std::unordered_map &objects_to_exclude, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const; [[nodiscard]] virtual collection_cache get_cache() const { return {}; } @@ -79,9 +80,10 @@ class priority_collection : public collection { void match(std::vector &events /* output */, std::unordered_set &seen_actions /* input & output */, - const object_store &store, const ddwaf::manifest &manifest, collection_cache &cache, + const object_store &store, collection_cache &cache, const std::unordered_set &rules_to_exclude, const std::unordered_map &objects_to_exclude, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const override; [[nodiscard]] collection_cache get_cache() const override { return {false, {}, actions_}; } diff --git a/src/condition.cpp b/src/condition.cpp index 6a04865af..612d95a54 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -15,15 +15,9 @@ namespace ddwaf { -std::optional condition::match_object(const ddwaf_object *object) const +std::optional condition::match_object( + const ddwaf_object *object, const rule_processor::base::ptr &processor) const { - decltype(processor_) processor; - if (mutable_) { - processor = std::atomic_load(&processor_); - } else { - processor = processor_; - } - const bool has_transform = !transformers_.empty(); bool transform_required = false; @@ -66,7 +60,8 @@ std::optional condition::match_object(const ddwaf_object *object) } template -std::optional condition::match_target(T &it, ddwaf::timer &deadline) const +std::optional condition::match_target( + T &it, const rule_processor::base::ptr &processor, ddwaf::timer &deadline) const { for (; it; ++it) { if (deadline.expired()) { @@ -77,7 +72,7 @@ std::optional condition::match_target(T &it, ddwaf::timer &deadlin continue; } - auto optional_match = match_object(*it); + auto optional_match = match_object(*it, processor); if (!optional_match.has_value()) { continue; } @@ -90,12 +85,33 @@ std::optional condition::match_target(T &it, ddwaf::timer &deadlin return std::nullopt; } +const rule_processor::base::ptr &condition::get_processor( + const std::unordered_map &dynamic_processors) const +{ + if (processor_ || data_id_.empty()) { + return processor_; + } + + auto it = dynamic_processors.find(data_id_); + if (it == dynamic_processors.end()) { + return processor_; + } + + return it->second; +} + std::optional condition::match(const object_store &store, - const ddwaf::manifest &manifest, const std::unordered_set &objects_excluded, bool run_on_new, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const { - for (const auto &target : targets_) { + const auto &processor = get_processor(dynamic_processors); + if (!processor) { + DDWAF_DEBUG("Condition doesn't have a valid processor"); + return std::nullopt; + } + + for (const auto &[target, name, key_path] : targets_) { if (deadline.expired()) { throw ddwaf::timeout_exception(); } @@ -106,8 +122,6 @@ std::optional condition::match(const object_store &store, continue; } - const auto &info = manifest.get_target_info(target); - // TODO: iterators could be cached to avoid reinitialisation const auto *object = store.get_target(target); if (object == nullptr) { @@ -116,17 +130,17 @@ std::optional condition::match(const object_store &store, std::optional optional_match; if (source_ == data_source::keys) { - object::key_iterator it(object, info.key_path, objects_excluded, limits_); - optional_match = match_target(it, deadline); + object::key_iterator it(object, key_path, objects_excluded, limits_); + optional_match = match_target(it, processor, deadline); } else { - object::value_iterator it(object, info.key_path, objects_excluded, limits_); - optional_match = match_target(it, deadline); + object::value_iterator it(object, key_path, objects_excluded, limits_); + optional_match = match_target(it, processor, deadline); } if (optional_match.has_value()) { - optional_match->source = info.name; + optional_match->source = name; - DDWAF_TRACE("Target %s matched parameter value %s", info.name.c_str(), + DDWAF_TRACE("Target %s matched parameter value %s", name.c_str(), optional_match->resolved.c_str()); return optional_match; } diff --git a/src/condition.hpp b/src/condition.hpp index 4bcab545d..5ac0657f7 100644 --- a/src/condition.hpp +++ b/src/condition.hpp @@ -26,16 +26,21 @@ namespace ddwaf { class condition { public: using ptr = std::shared_ptr; + struct target_type { + manifest::target_type root; + std::string name; + std::vector key_path; + }; enum class data_source : uint8_t { values, keys }; - condition(std::vector &&targets, - std::vector &&transformers, - std::shared_ptr &&processor, + condition(std::vector targets, std::vector transformers, + std::shared_ptr processor, std::string data_id = {}, ddwaf::object_limits limits = ddwaf::object_limits(), - data_source source = data_source::values, bool is_mutable = false) + data_source source = data_source::values) : targets_(std::move(targets)), transformers_(std::move(transformers)), - processor_(std::move(processor)), limits_(limits), source_(source), mutable_(is_mutable) + processor_(std::move(processor)), data_id_(std::move(data_id)), limits_(limits), + source_(source) {} ~condition() = default; @@ -45,44 +50,32 @@ class condition { condition(const condition &) = delete; condition &operator=(const condition &) = delete; - std::optional match(const object_store &store, const ddwaf::manifest &manifest, + std::optional match(const object_store &store, const std::unordered_set &objects_excluded, bool run_on_new, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const; - std::string_view processor_name() + [[nodiscard]] const std::vector &get_targets() const { - if (mutable_) { - return std::atomic_load(&processor_)->name(); - } - - return processor_->name(); - } - - void reset_processor(std::shared_ptr &proc) - { - if (!mutable_) { - throw std::runtime_error("Attempting to mutate an immutable " - "condition with processor " + - std::string(processor_->name())); - } - - std::atomic_store(&processor_, proc); + return targets_; } - const std::vector &get_targets() { return targets_; } - protected: - std::optional match_object(const ddwaf_object *object) const; + std::optional match_object( + const ddwaf_object *object, const rule_processor::base::ptr &processor) const; template - std::optional match_target(T &it, ddwaf::timer &deadline) const; + std::optional match_target( + T &it, const rule_processor::base::ptr &processor, ddwaf::timer &deadline) const; - std::vector targets_; + [[nodiscard]] const rule_processor::base::ptr &get_processor( + const std::unordered_map &dynamic_processors) const; + std::vector targets_; std::vector transformers_; std::shared_ptr processor_; + std::string data_id_; ddwaf::object_limits limits_; data_source source_; - bool mutable_; }; } // namespace ddwaf diff --git a/src/config.hpp b/src/config.hpp index f69fb85a0..1bc3afa13 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -17,10 +17,4 @@ struct object_limits { uint32_t max_string_length{DDWAF_MAX_STRING_LENGTH}; }; -struct config { - ddwaf::object_limits limits; - ddwaf::obfuscator event_obfuscator; - ddwaf_object_free_fn free_fn{ddwaf_object_free}; -}; - } // namespace ddwaf diff --git a/src/context.cpp b/src/context.cpp index 47e1e99e5..e4722f4d9 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -46,7 +46,7 @@ DDWAF_RET_CODE context::run( return DDWAF_OK; } - const event_serializer serializer(config_.event_obfuscator); + const event_serializer serializer(*ruleset_->event_obfuscator); std::vector events; try { @@ -68,7 +68,7 @@ DDWAF_RET_CODE context::run( const std::unordered_set &context::filter_rules(ddwaf::timer &deadline) { - for (const auto &[id, filter] : ruleset_.rule_filters) { + for (const auto &[id, filter] : ruleset_->rule_filters) { if (deadline.expired()) { DDWAF_INFO("Ran out of time while evaluating rule filters"); throw timeout_exception(); @@ -81,7 +81,7 @@ const std::unordered_set &context::filter_rules(ddwaf::timer &deadlin } rule_filter::cache_type &cache = it->second; - auto exclusion = filter->match(store_, ruleset_.manifest, cache, deadline); + auto exclusion = filter->match(store_, cache, deadline); rules_to_exclude_.merge(exclusion); } return rules_to_exclude_; @@ -90,7 +90,7 @@ const std::unordered_set &context::filter_rules(ddwaf::timer &deadlin const std::unordered_map &context::filter_inputs( const std::unordered_set &rules_to_exclude, ddwaf::timer &deadline) { - for (const auto &[id, filter] : ruleset_.input_filters) { + for (const auto &[id, filter] : ruleset_->input_filters) { if (deadline.expired()) { DDWAF_INFO("Ran out of time while evaluating input filters"); throw timeout_exception(); @@ -103,7 +103,7 @@ const std::unordered_map &context::filter_inputs } input_filter::cache_type &cache = it->second; - auto exclusion = filter->match(store_, ruleset_.manifest, cache, deadline); + auto exclusion = filter->match(store_, cache, deadline); if (exclusion.has_value()) { for (const auto &rule : exclusion->rules) { if (rules_to_exclude.find(rule) != rules_to_exclude.end()) { @@ -124,24 +124,27 @@ std::vector context::match(const std::unordered_set &rules_to_ { std::vector events; + for (const auto &[id, proc] : ruleset_->dynamic_processors) { + DDWAF_DEBUG("PROCESSORS: %s", id.c_str()); + } 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.get_cache()); it = new_it; } - collection.match(events, seen_actions_, store_, ruleset_.manifest, it->second, - rules_to_exclude, objects_to_exclude, deadline); + collection.match(events, seen_actions_, store_, it->second, rules_to_exclude, + objects_to_exclude, ruleset_->dynamic_processors, deadline); }; // Evaluate priority collections first - for (auto &[type, collection] : ruleset_.priority_collections) { + for (auto &[type, collection] : ruleset_->priority_collections) { DDWAF_DEBUG("Evaluating priority collection %s", type.data()); eval_collection(type, collection); } // Evalaute regular collection after - for (auto &[type, collection] : ruleset_.collections) { + for (auto &[type, collection] : ruleset_->collections) { DDWAF_DEBUG("Evaluating collection %s", type.data()); eval_collection(type, collection); } diff --git a/src/context.hpp b/src/context.hpp index 911381d2c..19d96f32f 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace ddwaf { @@ -25,14 +26,12 @@ class context { public: using object_set = std::unordered_set; - context( - ddwaf::ruleset &ruleset, const ddwaf::config &config, std::shared_ptr handle = nullptr) - : ruleset_(ruleset), config_(config), store_(ruleset_.manifest, config_.free_fn), - handle_(std::move(handle)) + explicit context(std::shared_ptr ruleset) + : ruleset_(std::move(ruleset)), store_(ruleset_->manifest, ruleset_->free_fn) { - rule_filter_cache_.reserve(ruleset_.rule_filters.size()); - input_filter_cache_.reserve(ruleset_.input_filters.size()); - collection_cache_.reserve(ruleset_.collections.size()); + rule_filter_cache_.reserve(ruleset_->rule_filters.size()); + input_filter_cache_.reserve(ruleset_->input_filters.size()); + collection_cache_.reserve(ruleset_->collections.size()); } context(const context &) = delete; @@ -56,8 +55,7 @@ class context { protected: bool is_first_run() const { return collection_cache_.empty(); } - ddwaf::ruleset &ruleset_; - const ddwaf::config &config_; + std::shared_ptr ruleset_; ddwaf::object_store store_; using input_filter = exclusion::input_filter; diff --git a/src/exclusion/input_filter.cpp b/src/exclusion/input_filter.cpp index 4b87e0d18..54059a998 100644 --- a/src/exclusion/input_filter.cpp +++ b/src/exclusion/input_filter.cpp @@ -11,8 +11,14 @@ namespace ddwaf::exclusion { using excluded_set = input_filter::excluded_set; -std::optional input_filter::match(const object_store &store, - const ddwaf::manifest &manifest, cache_type &cache, ddwaf::timer &deadline) const +input_filter::input_filter(std::string id, std::vector conditions, + std::set rule_targets, std::shared_ptr filter) + : id_(std::move(id)), conditions_(std::move(conditions)), + rule_targets_(std::move(rule_targets)), filter_(std::move(filter)) +{} + +std::optional input_filter::match( + const object_store &store, cache_type &cache, ddwaf::timer &deadline) const { if (!cache.result) { for (const auto &cond : conditions_) { @@ -31,7 +37,7 @@ std::optional input_filter::match(const object_store &store, } // TODO: Condition interface without events - auto opt_match = cond->match(store, manifest, {}, run_on_new, deadline); + auto opt_match = cond->match(store, {}, run_on_new, {}, deadline); if (!opt_match.has_value()) { cached_result->second = false; return std::nullopt; @@ -42,7 +48,7 @@ std::optional input_filter::match(const object_store &store, cache.result = true; } - auto objects = filter_.match(store, cache.object_filter_cache, deadline); + auto objects = filter_->match(store, cache.object_filter_cache, deadline); if (objects.empty()) { return std::nullopt; diff --git a/src/exclusion/input_filter.hpp b/src/exclusion/input_filter.hpp index d56200d61..4485540d7 100644 --- a/src/exclusion/input_filter.hpp +++ b/src/exclusion/input_filter.hpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -33,14 +32,11 @@ class input_filter { object_filter::cache_type object_filter_cache; }; - input_filter(std::string &&id, std::vector &&conditions, - std::set &&rule_targets, object_filter &&filter) - : id_(std::move(id)), conditions_(std::move(conditions)), - rule_targets_(std::move(rule_targets)), filter_(std::move(filter)) - {} + input_filter(std::string id, std::vector conditions, + std::set rule_targets, std::shared_ptr filter); - std::optional match(const object_store &store, const ddwaf::manifest &manifest, - cache_type &cache, ddwaf::timer &deadline) const; + std::optional match( + const object_store &store, cache_type &cache, ddwaf::timer &deadline) const; std::string_view get_id() { return id_; } @@ -48,7 +44,7 @@ class input_filter { std::string id_; std::vector conditions_; const std::set rule_targets_; - object_filter filter_; + std::shared_ptr filter_; }; } // namespace ddwaf::exclusion diff --git a/src/exclusion/object_filter.hpp b/src/exclusion/object_filter.hpp index a585d867f..bbbd876f3 100644 --- a/src/exclusion/object_filter.hpp +++ b/src/exclusion/object_filter.hpp @@ -244,14 +244,18 @@ class object_filter { void insert(manifest::target_type target, const std::vector &key_path = {}) { target_paths_[target].insert(key_path); + targets_.emplace(target); } std::unordered_set match( const object_store &store, cache_type &cache, ddwaf::timer &deadline) const; + const std::unordered_set &get_targets() const { return targets_; } + protected: object_limits limits_; std::unordered_map target_paths_; + std::unordered_set targets_; }; } // namespace ddwaf::exclusion diff --git a/src/exclusion/rule_filter.cpp b/src/exclusion/rule_filter.cpp index 19d9aa613..6b59779ec 100644 --- a/src/exclusion/rule_filter.cpp +++ b/src/exclusion/rule_filter.cpp @@ -10,7 +10,7 @@ namespace ddwaf::exclusion { rule_filter::rule_filter( - std::string &&id, std::vector &&conditions, std::set &&rule_targets) + std::string id, std::vector conditions, std::set rule_targets) : id_(std::move(id)), conditions_(std::move(conditions)) { rule_targets_.reserve(rule_targets.size()); @@ -18,8 +18,9 @@ rule_filter::rule_filter( rule_targets_.emplace(std::move(rule_targets.extract(it++).value())); } } -std::unordered_set rule_filter::match(const object_store &store, - const ddwaf::manifest &manifest, cache_type &cache, ddwaf::timer &deadline) const + +std::unordered_set rule_filter::match( + const object_store &store, cache_type &cache, ddwaf::timer &deadline) const { if (cache.result) { return {}; @@ -41,7 +42,7 @@ std::unordered_set rule_filter::match(const object_store &store, } // TODO: Condition interface without events - auto opt_match = cond->match(store, manifest, {}, run_on_new, deadline); + auto opt_match = cond->match(store, {}, run_on_new, {}, deadline); if (!opt_match.has_value()) { cached_result->second = false; return {}; diff --git a/src/exclusion/rule_filter.hpp b/src/exclusion/rule_filter.hpp index c61d2669f..624c9c229 100644 --- a/src/exclusion/rule_filter.hpp +++ b/src/exclusion/rule_filter.hpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -26,11 +25,11 @@ class rule_filter { std::unordered_map conditions; }; - rule_filter(std::string &&id, std::vector &&conditions, - std::set &&rule_targets); + rule_filter( + std::string id, std::vector conditions, std::set rule_targets); - std::unordered_set match(const object_store &store, const ddwaf::manifest &manifest, - cache_type &cache, ddwaf::timer &deadline) const; + std::unordered_set match( + const object_store &store, cache_type &cache, ddwaf::timer &deadline) const; std::string_view get_id() { return id_; } diff --git a/src/interface.cpp b/src/interface.cpp index 23fa8de16..432276d2f 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -37,99 +38,110 @@ const char *log_level_to_str(DDWAF_LOG_LEVEL level) return "off"; } -} // namespace -#endif -// explicit instantiation declaration to suppress warning -extern "C" { -ddwaf_handle ddwaf_init( - const ddwaf_object *rule, const ddwaf_config *config, ddwaf_ruleset_info *info) + +std::shared_ptr obfuscator_from_config(const ddwaf_config *config) { - try { - if (rule != nullptr) { - ddwaf::ruleset_info ri(info); - auto waf_ptr = ddwaf::waf::from_config(*rule, config, ri); - if (waf_ptr) { - return new std::shared_ptr(std::move(waf_ptr)); - } + std::string_view key_regex; + std::string_view value_regex; + + if (config != nullptr) { + if (config->obfuscator.key_regex != nullptr) { + key_regex = config->obfuscator.key_regex; + } + + if (config->obfuscator.value_regex != nullptr) { + value_regex = config->obfuscator.value_regex; } - } catch (const std::exception &e) { - DDWAF_ERROR("%s", e.what()); - } catch (...) { - DDWAF_ERROR("unknown exception"); } - return nullptr; + return std::make_shared(key_regex, value_regex); } -void ddwaf_destroy(ddwaf_handle handle) +ddwaf::object_limits limits_from_config(const ddwaf_config *config) { - if (handle == nullptr) { - return; + ddwaf::object_limits limits; + + if (config != nullptr) { + if (config->limits.max_container_size != 0) { + limits.max_container_size = config->limits.max_container_size; + } + + if (config->limits.max_container_depth != 0) { + limits.max_container_depth = config->limits.max_container_depth; + } + + if (config->limits.max_string_length != 0) { + limits.max_string_length = config->limits.max_string_length; + } } + return limits; +} + +} // namespace + +#endif +// explicit instantiation declaration to suppress warning +extern "C" { +ddwaf::waf *ddwaf_init( + const ddwaf_object *ruleset, const ddwaf_config *config, ddwaf_ruleset_info *info) +{ try { - delete handle; + if (ruleset != nullptr) { + ddwaf::ruleset_info ri(info); + ddwaf::parameter input = *ruleset; + return new ddwaf::waf(input, ri, limits_from_config(config), + config != nullptr ? config->free_fn : ddwaf_object_free, + obfuscator_from_config(config)); + } } catch (const std::exception &e) { DDWAF_ERROR("%s", e.what()); } catch (...) { DDWAF_ERROR("unknown exception"); } + + return nullptr; } -DDWAF_RET_CODE ddwaf_update_rule_data(ddwaf_handle handle, ddwaf_object *data) +ddwaf::waf *ddwaf_update(ddwaf::waf *handle, const ddwaf_object *ruleset, ddwaf_ruleset_info *info) { - if (handle == nullptr || *handle == nullptr || data == nullptr) { - return DDWAF_ERR_INVALID_ARGUMENT; - } - try { - ddwaf::parameter param = *data; - (*handle)->update_rule_data(param); - } catch (const ddwaf::bad_cast &e) { - DDWAF_ERROR("%s", e.what()); - return DDWAF_ERR_INVALID_OBJECT; + if (handle != nullptr && ruleset != nullptr) { + ddwaf::ruleset_info ri(info); + ddwaf::parameter input = *ruleset; + return handle->update(input, ri); + } } catch (const std::exception &e) { DDWAF_ERROR("%s", e.what()); - return DDWAF_ERR_INTERNAL; } catch (...) { DDWAF_ERROR("unknown exception"); - return DDWAF_ERR_INTERNAL; } - - return DDWAF_OK; + return nullptr; } -DDWAF_RET_CODE ddwaf_toggle_rules(ddwaf_handle handle, ddwaf_object *rule_map) +void ddwaf_destroy(ddwaf::waf *handle) { - if (handle == nullptr || *handle == nullptr || rule_map == nullptr) { - return DDWAF_ERR_INVALID_ARGUMENT; + if (handle == nullptr) { + return; } try { - ddwaf::parameter param = *rule_map; - (*handle)->toggle_rules(param); - } catch (const ddwaf::bad_cast &e) { - DDWAF_ERROR("%s", e.what()); - return DDWAF_ERR_INVALID_OBJECT; + delete handle; } catch (const std::exception &e) { DDWAF_ERROR("%s", e.what()); - return DDWAF_ERR_INTERNAL; } catch (...) { DDWAF_ERROR("unknown exception"); - return DDWAF_ERR_INTERNAL; } - - return DDWAF_OK; } -const char *const *ddwaf_required_addresses(ddwaf::waf::ptr *handle, uint32_t *size) +const char *const *ddwaf_required_addresses(ddwaf::waf *handle, uint32_t *size) { - if (handle == nullptr || *handle == nullptr) { + if (handle == nullptr) { *size = 0; return nullptr; } - const auto &addresses = (*handle)->get_root_addresses(); + const auto &addresses = handle->get_root_addresses(); if (addresses.empty() || addresses.size() > std::numeric_limits::max()) { *size = 0; return nullptr; @@ -139,28 +151,11 @@ const char *const *ddwaf_required_addresses(ddwaf::waf::ptr *handle, uint32_t *s return addresses.data(); } -const char *const *ddwaf_required_rule_data_ids(ddwaf::waf::ptr *handle, uint32_t *size) -{ - if (handle == nullptr || *handle == nullptr) { - *size = 0; - return nullptr; - } - - const auto &ids = (*handle)->get_rule_data_ids(); - if (ids.empty() || ids.size() > std::numeric_limits::max()) { - *size = 0; - return nullptr; - } - - *size = (uint32_t)ids.size(); - return ids.data(); -} - -ddwaf_context ddwaf_context_init(ddwaf::waf::ptr *handle) +ddwaf_context ddwaf_context_init(ddwaf::waf *handle) { try { - if (handle != nullptr && *handle != nullptr) { - return new ddwaf::context((*handle)->create_context()); + if (handle != nullptr) { + return new ddwaf::context(handle->create_context()); } } catch (const std::exception &e) { DDWAF_ERROR("%s", e.what()); diff --git a/src/manifest.cpp b/src/manifest.cpp index 9ca8ca6c9..b6f94b95e 100644 --- a/src/manifest.cpp +++ b/src/manifest.cpp @@ -8,50 +8,34 @@ #include namespace ddwaf { - -manifest::target_type manifest_builder::insert( - const std::string &root, const std::vector &key_path) +manifest::target_type manifest::insert(const std::string &root) { auto it = targets_.find(root); if (it == targets_.end()) { - auto [new_it, res] = targets_.emplace(root, target_spec{++index_, 0, {}}); - // assume res = true + auto [new_it, res] = targets_.emplace(root, ++index_); it = new_it; } + return it->second; +} - auto &[root_id, derived_id, derived_map] = it->second; - - if (key_path.empty()) { - return generate_target(root_id, 0); - } - - auto derived_it = derived_map.find(key_path); - if (derived_it == derived_map.end()) { - auto [new_it, res] = derived_map.emplace(key_path, ++derived_id); - derived_it = new_it; +std::optional manifest::find(const std::string &root) const +{ + auto it = targets_.find(root); + if (it == targets_.end()) { + return std::nullopt; } - - return generate_target(root_id, derived_it->second); + return {it->second}; } -manifest manifest_builder::build_manifest() +void manifest::remove_unused(const std::unordered_set &valid_targets) { - std::unordered_map targets; - std::unordered_map info; - - for (auto &[key, spec] : targets_) { - auto root = generate_target(spec.root_id, 0); - - targets.emplace(key, root); - info.emplace(root, manifest::target_info{key, {}}); - - for (auto &[key_path, derived_id] : spec.derived) { - auto derived = generate_target(spec.root_id, derived_id); - info.emplace(derived, manifest::target_info{key, key_path}); + for (auto it = targets_.begin(); it != targets_.end();) { + if (valid_targets.find(it->second) == valid_targets.end()) { + it = targets_.erase(it); + } else { + ++it; } } - - return {std::move(targets), std::move(info)}; } } // namespace ddwaf diff --git a/src/manifest.hpp b/src/manifest.hpp index 04f5daeb7..f7f76114a 100644 --- a/src/manifest.hpp +++ b/src/manifest.hpp @@ -16,112 +16,38 @@ #include -// NOLINTNEXTLINE(cert-dcl58-cpp) -namespace std { -// NOLINTNEXTLINE(cert-dcl58-cpp) -template <> struct hash> { - std::size_t operator()(const std::vector &k) const - { - std::size_t hash = 0; - for (const auto &str : k) { hash ^= std::hash{}(str); } - return hash; - } -}; -} // namespace std - namespace ddwaf { + +// TODO bring back manifest builder? class manifest { public: using target_type = uint32_t; - struct target_info { - std::string name; - std::vector key_path; - }; - - manifest() = default; - manifest(std::unordered_map &&targets, - std::unordered_map &&info) - : targets_(std::move(targets)), info_(std::move(info)) - { - root_addresses_.reserve(targets_.size()); - for (auto &[k, v] : targets_) { root_addresses_.push_back(k.c_str()); } - } - ~manifest() = default; - - manifest(manifest &&) = default; - manifest(const manifest &) = delete; - manifest &operator=(manifest &&) = default; - manifest &operator=(const manifest &) = delete; - - bool empty() { return targets_.empty(); } - bool contains(const std::string &name) { return targets_.find(name) != targets_.end(); } - - std::pair get_target(const std::string &name) const - { - auto it = targets_.find(name); - if (it == targets_.end()) { - return {false, 0}; - } - return {true, it->second}; - } - - const target_info &get_target_info(target_type target) const + manifest::target_type insert(const std::string &root); + std::optional find(const std::string &root) const; + + // Remove unused targets + void remove_unused(const std::unordered_set &valid_targets); + + // TODO This is problematic because if the manifest is modified after the + // first call to this function, the array will be out-of-sync. At the + // same time we can't invalidate it. + // This is not really a problem in practice since each ruleset has its + // own manifest copy, but it would be better to avoid an inconsistent + // interface that could be misused. + const std::vector &get_root_addresses() { - static const target_info empty_info = {}; - auto it = info_.find(target); - if (it == info_.end()) { - return empty_info; + if (root_addresses_.empty()) { + for (const auto &[id, target] : targets_) { root_addresses_.emplace_back(id.c_str()); } } - return it->second; + return root_addresses_; } - const std::vector &get_root_addresses() const { return root_addresses_; } - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - static target_type get_root(target_type target) { return target & 0xFFFF0000; } - protected: - std::unordered_map targets_{}; - std::unordered_map info_{}; + std::unordered_map targets_; + target_type index_{0}; // Root address memory to be returned to the API caller std::vector root_addresses_; }; -class manifest_builder { -public: - manifest::target_type insert(const std::string &root, const std::vector &key_path); - - manifest build_manifest(); - - std::optional find(const std::string &root) - { - auto it = targets_.find(root); - if (it == targets_.end()) { - return std::nullopt; - } - return generate_target(it->second.root_id, 0); - } - -protected: - // The spec allows keeping track of targets which share the same root - // address, but a different key_path. The root target ID is always - // the same for all of them, but the derived ID is specific to - // each key_path. - struct target_spec { - uint16_t root_id; - uint16_t derived_id{0}; - std::unordered_map, uint16_t> derived; - }; - - static constexpr manifest::target_type generate_target(uint16_t root, uint16_t id) - { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - return static_cast(root) << 16 | id; - } - - std::unordered_map targets_; - uint16_t index_{0}; -}; - } // namespace ddwaf diff --git a/src/mkmap.hpp b/src/mkmap.hpp new file mode 100644 index 000000000..a195edf45 --- /dev/null +++ b/src/mkmap.hpp @@ -0,0 +1,125 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#pragma once + +#include +#include +#include +#include + +#include + +namespace ddwaf { +template , + typename = std::enable_if_t>>>> +class multi_key_map { +public: + template ::value, + typename U::iterator>> + void insert(const U &keys, const T &value) + { + for (const auto &key : keys) { data_[key.first][key.second].emplace(value); } + } + + template std::set find(const std::pair &key) const + { + auto first_it = data_.find(key.first); + if (first_it == data_.end()) { + return {}; + } + + const auto &second_data = first_it->second; + auto second_it = second_data.find(key.second); + if (second_it == second_data.end()) { + return {}; + } + + return second_it->second; + } + + template + const std::set &find_ref(const std::pair &key) const + { + static std::set empty; + auto first_it = data_.find(Key(key.first)); + if (first_it == data_.end()) { + return empty; + } + + const auto &second_data = first_it->second; + auto second_it = second_data.find(Key(key.second)); + if (second_it == second_data.end()) { + return empty; + } + + return second_it->second; + } + + template + std::set find2(const std::pair &key0, + const std::pair &key1) const + { + const auto &set0 = find_ref(key0); + if (set0.empty()) { + return {}; + } + + const auto &set1 = find_ref(key1); + if (set1.empty()) { + return {}; + } + + std::set result; + std::set_intersection(set0.begin(), set0.end(), set1.begin(), set1.end(), + std::inserter(result, result.begin())); + return result; + } + + template std::set multifind(const U &keys) const + { + std::pair first = *keys.begin(); + + switch (keys.size()) { + case 0: + return {}; + case 1: + return find(first); + case 2: { + std::pair second = *(++keys.begin()); + return find2(first, second); + } + } + + std::set latest = find(first); + if (latest.empty()) { + return {}; + } + + std::set current; + for (const std::pair key : keys) { + const auto &next = find_ref(key); + if (next.empty()) { + return {}; + } + + std::set_intersection(latest.begin(), latest.end(), next.begin(), next.end(), + std::inserter(current, current.begin())); + std::swap(latest, current); + current.clear(); + } + + return latest; + } + + void clear() { data_.clear(); } + +protected: + std::unordered_map>> data_; +}; + +} // namespace ddwaf diff --git a/src/object_store.cpp b/src/object_store.cpp index ae2921210..c1c479b2b 100644 --- a/src/object_store.cpp +++ b/src/object_store.cpp @@ -62,11 +62,12 @@ bool object_store::insert(const ddwaf_object &input) } std::string key(array[i].parameterName, length); - auto [res, target] = manifest_.get_target(key); - if (!res) { + auto opt_target = manifest_.find(key); + if (!opt_target.has_value()) { continue; } + auto target = *opt_target; objects_[target] = &array[i]; latest_batch_.emplace(target); } @@ -76,7 +77,7 @@ bool object_store::insert(const ddwaf_object &input) const ddwaf_object *object_store::get_target(manifest::target_type target) const { - auto it = objects_.find(manifest::get_root(target)); + auto it = objects_.find(target); return it != objects_.end() ? it->second : nullptr; } diff --git a/src/object_store.hpp b/src/object_store.hpp index a1585e8a9..8007c6708 100644 --- a/src/object_store.hpp +++ b/src/object_store.hpp @@ -25,7 +25,7 @@ class object_store { bool is_new_target(const manifest::target_type target) const { - return latest_batch_.find(manifest::get_root(target)) != latest_batch_.cend(); + return latest_batch_.find(target) != latest_batch_.cend(); } bool has_new_targets() const { return !latest_batch_.empty(); } diff --git a/src/parameter.cpp b/src/parameter.cpp index aba7117bc..41c87b0bb 100644 --- a/src/parameter.cpp +++ b/src/parameter.cpp @@ -91,7 +91,7 @@ static void print_(parameter args, uint64_t depth) void parameter::print() { print_(*this, 0); } -parameter::operator parameter::map() +parameter::operator parameter::map() const { if (type != DDWAF_OBJ_MAP) { throw bad_cast("map", strtype(type)); @@ -115,7 +115,7 @@ parameter::operator parameter::map() return map; } -parameter::operator parameter::vector() +parameter::operator parameter::vector() const { if (type != DDWAF_OBJ_ARRAY) { throw bad_cast("array", strtype(type)); @@ -127,7 +127,7 @@ parameter::operator parameter::vector() return std::vector(array, array + nbEntries); } -parameter::operator parameter::string_set() +parameter::operator parameter::string_set() const { if (type != DDWAF_OBJ_ARRAY) { throw bad_cast("array", strtype(type)); @@ -150,7 +150,7 @@ parameter::operator parameter::string_set() return set; } -parameter::operator std::string_view() +parameter::operator std::string_view() const { if (type != DDWAF_OBJ_STRING || stringValue == nullptr) { throw bad_cast("string", strtype(type)); @@ -159,7 +159,7 @@ parameter::operator std::string_view() return {stringValue, static_cast(nbEntries)}; } -parameter::operator std::string() +parameter::operator std::string() const { if (type != DDWAF_OBJ_STRING || stringValue == nullptr) { throw bad_cast("string", strtype(type)); @@ -168,7 +168,7 @@ parameter::operator std::string() return {stringValue, static_cast(nbEntries)}; } -parameter::operator uint64_t() +parameter::operator uint64_t() const { if (type == DDWAF_OBJ_UNSIGNED) { return uintValue; @@ -186,7 +186,7 @@ parameter::operator uint64_t() throw bad_cast("unsigned", strtype(type)); } -parameter::operator int64_t() +parameter::operator int64_t() const { if (type == DDWAF_OBJ_SIGNED) { return intValue; @@ -204,7 +204,7 @@ parameter::operator int64_t() throw bad_cast("signed", strtype(type)); } -parameter::operator bool() +parameter::operator bool() const { if (type == DDWAF_OBJ_BOOL) { return boolean; @@ -232,7 +232,7 @@ parameter::operator bool() throw bad_cast("bool", strtype(type)); } -parameter::operator std::vector() +parameter::operator std::vector() const { if (type != DDWAF_OBJ_ARRAY) { throw bad_cast("array", strtype(type)); @@ -255,7 +255,7 @@ parameter::operator std::vector() return data; } -parameter::operator std::vector() +parameter::operator std::vector() const { if (type != DDWAF_OBJ_ARRAY) { throw bad_cast("array", strtype(type)); diff --git a/src/parameter.hpp b/src/parameter.hpp index d59f58fb5..5bbab9212 100644 --- a/src/parameter.hpp +++ b/src/parameter.hpp @@ -18,9 +18,9 @@ namespace ddwaf { class parameter : public ddwaf_object { public: - typedef std::unordered_map map; - typedef std::vector vector; - typedef std::unordered_set string_set; + using map = std::unordered_map; + using vector = std::vector; + using string_set = std::unordered_set; parameter() = default; parameter(const ddwaf_object &arg) { *((ddwaf_object *)this) = arg; } @@ -33,16 +33,16 @@ class parameter : public ddwaf_object { void print(); - operator map(); - operator vector(); - operator string_set(); - operator std::string_view(); - operator std::string(); - operator uint64_t(); - operator int64_t(); - operator bool(); - operator std::vector(); - operator std::vector(); + explicit operator map() const; + explicit operator vector() const; + explicit operator string_set() const; + explicit operator std::string_view() const; + explicit operator std::string() const; + explicit operator uint64_t() const; + explicit operator int64_t() const; + explicit operator bool() const; + explicit operator std::vector() const; + explicit operator std::vector() const; ~parameter() = default; }; diff --git a/src/parser/common.hpp b/src/parser/common.hpp index 23a8c81f0..13c586e73 100644 --- a/src/parser/common.hpp +++ b/src/parser/common.hpp @@ -8,13 +8,15 @@ #include #include + #include namespace ddwaf::parser { -template T at(parameter::map &map, const std::string &key) + +template T at(const parameter::map &map, const std::string &key) { try { - return map.at(key); + return static_cast(map.at(key)); } catch (const std::out_of_range &) { throw missing_key(key); } catch (const bad_cast &e) { @@ -22,11 +24,11 @@ template T at(parameter::map &map, const std::string &key) } } -template T at(parameter::map &map, const std::string &key, const T &default_) +template T at(const parameter::map &map, const std::string &key, const T &default_) { try { auto it = map.find(key); - return it == map.end() ? default_ : it->second; + return it == map.end() ? default_ : static_cast(it->second); } catch (const bad_cast &e) { throw invalid_type(key, e); } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 9486f1bf0..b66794c3b 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include #include #include #include @@ -12,33 +13,24 @@ namespace ddwaf::parser { -namespace v1 { -void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, ddwaf::config &cfg); -} - -namespace v2 { -void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, ddwaf::config &cfg); -} - -void parse(parameter object, ruleset_info &info, ddwaf::ruleset &rs, ddwaf::config &cfg) +unsigned parse_schema_version(parameter::map &ruleset) { - parameter::map ruleset = object; - std::string_view version = at(ruleset, "version"); + auto version = at(ruleset, "version"); - uint16_t major, minor; - if (std::sscanf(version.data(), "%hu.%hu", &major, &minor) != 2) { + auto dot_pos = version.find('.'); + if (dot_pos == std::string_view::npos) { throw parsing_error("invalid version format, expected major.minor"); } + version.remove_suffix(version.size() - dot_pos); - switch (major) { - case 1: - return v1::parse(ruleset, info, rs, cfg); - case 2: - return v2::parse(ruleset, info, rs, cfg); - default: - DDWAF_ERROR("incompatible ruleset version %u.%u", major, minor); - throw unsupported_version(); + unsigned major; + const char *data = version.data(); + const char *end = data + version.size(); + if (std::from_chars(data, end, major).ec != std::errc{}) { + throw parsing_error("invalid version format, expected major.minor"); } + + return major; } } // namespace ddwaf::parser diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index a9e8e93c2..354a7b3c7 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -6,6 +6,7 @@ #pragma once +#include "parser/specification.hpp" #include #include #include @@ -17,6 +18,24 @@ namespace ddwaf::parser { -void parse(parameter input, ruleset_info &info, ddwaf::ruleset &rs, ddwaf::config &cfg); +unsigned parse_schema_version(parameter::map &ruleset); -} +namespace v1 { +void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, object_limits limits); +} // namespace v1 + +namespace v2 { +rule_spec_container parse_rules(parameter::vector &rule_array, ddwaf::ruleset_info &info, + manifest &target_manifest, std::unordered_map &rule_data_ids, + const object_limits &limits); + +rule_data_container parse_rule_data( + parameter::vector &rule_data, std::unordered_map &rule_data_ids); + +override_spec_container parse_overrides(parameter::vector &override_array); + +filter_spec_container parse_filters( + parameter::vector &filter_array, manifest &target_manifest, const object_limits &limits); + +} // namespace v2 +} // namespace ddwaf::parser diff --git a/src/parser/parser_v1.cpp b/src/parser/parser_v1.cpp index f19183b0a..a82174cd6 100644 --- a/src/parser/parser_v1.cpp +++ b/src/parser/parser_v1.cpp @@ -21,18 +21,14 @@ #include #include -using ddwaf::manifest; -using ddwaf::manifest_builder; -using ddwaf::parameter; -using ddwaf::parser::at; using ddwaf::rule_processor::base; namespace ddwaf::parser::v1 { namespace { -condition::ptr parseCondition(parameter::map &rule, manifest_builder &mb, - std::vector transformers, ddwaf::config &cfg) +condition::ptr parseCondition(parameter::map &rule, manifest &target_manifest, + std::vector transformers, ddwaf::object_limits limits) { auto operation = at(rule, "operation"); auto params = at(rule, "parameters"); @@ -78,14 +74,17 @@ condition::ptr parseCondition(parameter::map &rule, manifest_builder &mb, throw ddwaf::parsing_error("unknown processor: " + std::string(operation)); } - std::vector targets; + std::vector targets; auto inputs = at(params, "inputs"); - for (std::string input : inputs) { + targets.reserve(inputs.size()); + for (const auto &input_param : inputs) { + auto input = static_cast(input_param); if (input.empty()) { throw ddwaf::parsing_error("empty address"); } - std::string root, key_path; + std::string root; + std::string key_path; size_t pos = input.find(':', 0); if (pos == std::string::npos || pos + 1 >= input.size()) { root = input; @@ -94,21 +93,21 @@ condition::ptr parseCondition(parameter::map &rule, manifest_builder &mb, key_path = input.substr(pos + 1, input.size()); } - manifest::target_type target; - if (key_path.empty()) { - target = mb.insert(root, {}); - } else { - target = mb.insert(root, {key_path}); + condition::target_type target; + target.root = target_manifest.insert(root); + target.name = std::move(root); + if (!key_path.empty()) { + target.key_path.emplace_back(key_path); } - targets.push_back(target); + targets.emplace_back(std::move(target)); } return std::make_shared( - std::move(targets), std::move(transformers), std::move(processor), cfg.limits); + std::move(targets), std::move(transformers), std::move(processor), std::string{}, limits); } -void parseRule(parameter::map &rule, ddwaf::ruleset_info &info, manifest_builder &mb, - ddwaf::ruleset &rs, ddwaf::config &cfg) +void parseRule(parameter::map &rule, ddwaf::ruleset_info &info, manifest &target_manifest, + ddwaf::ruleset &rs, ddwaf::object_limits limits) { auto id = at(rule, "id"); if (rs.rules.find(id) != rs.rules.end()) { @@ -120,7 +119,8 @@ void parseRule(parameter::map &rule, ddwaf::ruleset_info &info, manifest_builder try { std::vector rule_transformers; auto transformers = at(rule, "transformers", parameter::vector()); - for (std::string_view transformer : transformers) { + for (const auto &transformer_param : transformers) { + auto transformer = static_cast(transformer_param); PW_TRANSFORM_ID transform_id = PWTransformer::getIDForString(transformer); if (transform_id == PWT_INVALID) { throw ddwaf::parsing_error("invalid transformer" + std::string(transformer)); @@ -130,14 +130,27 @@ void parseRule(parameter::map &rule, ddwaf::ruleset_info &info, manifest_builder std::vector conditions; auto conditions_array = at(rule, "conditions"); - for (parameter::map cond : conditions_array) { - conditions.push_back(parseCondition(cond, mb, rule_transformers, cfg)); + conditions.reserve(conditions_array.size()); + for (const auto &cond_param : conditions_array) { + auto cond = static_cast(cond_param); + conditions.push_back(parseCondition(cond, target_manifest, rule_transformers, limits)); } - auto tags = at(rule, "tags"); - auto rule_ptr = std::make_shared(std::string(id), - at(rule, "name"), at(tags, "type"), - at(tags, "category", ""), std::move(conditions)); + std::unordered_map tags; + for (auto &[key, value] : at(rule, "tags")) { + try { + tags.emplace(key, std::string(value)); + } catch (const bad_cast &e) { + throw invalid_type(std::string(key), e); + } + } + + if (tags.find("type") == tags.end()) { + throw ddwaf::parsing_error("missing key 'type'"); + } + + auto rule_ptr = std::make_shared( + std::string(id), at(rule, "name"), std::move(tags), std::move(conditions)); rs.insert_rule(rule_ptr); info.add_loaded(); @@ -149,15 +162,15 @@ void parseRule(parameter::map &rule, ddwaf::ruleset_info &info, manifest_builder } // namespace -void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, ddwaf::config &cfg) +void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, object_limits limits) { auto rules_array = at(ruleset, "events"); rs.rules.reserve(rules_array.size()); - ddwaf::manifest_builder mb; - for (parameter::map rule : rules_array) { + for (const auto &rule_param : rules_array) { try { - parseRule(rule, info, mb, rs, cfg); + auto rule = static_cast(rule_param); + parseRule(rule, info, rs.manifest, rs, limits); } catch (const std::exception &e) { DDWAF_WARN("%s", e.what()); info.add_failed(); @@ -168,8 +181,6 @@ void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, ddwa throw ddwaf::parsing_error("no valid rules found"); } - rs.manifest = mb.build_manifest(); - DDWAF_DEBUG("Loaded %zu rules out of %zu available in the ruleset", rs.rules.size(), rules_array.size()); } diff --git a/src/parser/parser_v2.cpp b/src/parser/parser_v2.cpp index c8d026206..91e1c9e5e 100644 --- a/src/parser/parser_v2.cpp +++ b/src/parser/parser_v2.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include #include #include #include @@ -11,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -25,28 +29,19 @@ #include #include -using ddwaf::manifest; -using ddwaf::manifest_builder; -using ddwaf::parameter; -using ddwaf::parser::at; using ddwaf::rule_processor::base; namespace ddwaf::parser::v2 { namespace { -condition::ptr parse_condition(parameter::map &rule, rule_data::dispatcher &dispatcher, - manifest_builder &mb, ddwaf::config &cfg, - ddwaf::condition::data_source source = ddwaf::condition::data_source::values, - std::vector transformers = {}) +std::pair parse_processor( + std::string_view operation, const parameter::map ¶ms) { - auto operation = at(rule, "operator"); - auto params = at(rule, "parameters"); - bool is_mutable = false; - parameter::map options; std::shared_ptr processor; - std::optional rule_data_id = std::nullopt; + std::string rule_data_id; + if (operation == "phrase_match") { auto list = at(params, "list"); @@ -86,279 +81,460 @@ condition::ptr parse_condition(parameter::map &rule, rule_data::dispatcher &disp auto it = params.find("list"); if (it == params.end()) { rule_data_id = at(params, "data"); - processor = std::make_shared(); - is_mutable = true; } else { - processor = std::make_shared(it->second); + processor = std::make_shared( + static_cast>(it->second)); } } else if (operation == "exact_match") { auto it = params.find("list"); if (it == params.end()) { rule_data_id = at(params, "data"); - processor = std::make_shared(); - is_mutable = true; } else { - processor = std::make_shared(it->second); + processor = std::make_shared( + static_cast>(it->second)); } } else { throw ddwaf::parsing_error("unknown processor: " + std::string(operation)); } - std::vector targets; + return {std::move(rule_data_id), std::move(processor)}; +} + +condition::ptr parse_rule_condition(const parameter::map &root, manifest &target_manifest, + std::unordered_map &rule_data_ids, condition::data_source source, + std::vector transformers, const object_limits &limits) +{ + auto operation = at(root, "operator"); + auto params = at(root, "parameters"); + + auto [rule_data_id, processor] = parse_processor(operation, params); + if (!processor && !rule_data_id.empty()) { + rule_data_ids.emplace(rule_data_id, operation); + } + std::vector targets; auto inputs = at(params, "inputs"); if (inputs.empty()) { throw ddwaf::parsing_error("empty inputs"); } - for (parameter::map input : inputs) { + for (const auto &input_param : inputs) { + auto input = static_cast(input_param); auto address = at(input, "address"); - auto key_paths = at(input, "key_path", parameter::vector()); if (address.empty()) { throw ddwaf::parsing_error("empty address"); } - std::vector kp; - for (std::string path : key_paths) { + auto kp = at>(input, "key_path", {}); + for (const auto &path : kp) { if (path.empty()) { throw ddwaf::parsing_error("empty key_path"); } - - kp.push_back(std::move(path)); } - auto target = mb.insert(address, std::move(kp)); - targets.push_back(target); - } - - auto cond = std::make_shared(std::move(targets), std::move(transformers), - std::move(processor), cfg.limits, source, is_mutable); + condition::target_type target; + target.root = target_manifest.insert(address); + target.name = address; + target.key_path = std::move(kp); - if (rule_data_id.has_value()) { - if (operation == "ip_match") { - dispatcher.register_condition(*rule_data_id, cond); - } else if (operation == "exact_match") { - dispatcher.register_condition(*rule_data_id, cond); - } + targets.emplace_back(target); } - return cond; + return std::make_shared(std::move(targets), std::move(transformers), + std::move(processor), std::move(rule_data_id), limits, source); } -void parse_rule(parameter::map &rule, ddwaf::ruleset_info &info, manifest_builder &mb, - ddwaf::ruleset &rs, ddwaf::config &cfg) +rule_spec parse_rule(parameter::map &rule, manifest &target_manifest, + std::unordered_map &rule_data_ids, const object_limits &limits) { - auto id = at(rule, "id"); - if (rs.rules.find(id) != rs.rules.end()) { - DDWAF_WARN("duplicate rule %s", id.c_str()); - info.insert_error(id, "duplicate rule"); - return; - } - - try { - std::vector rule_transformers; - auto source = ddwaf::condition::data_source::values; - auto transformers = at(rule, "transformers", parameter::vector()); - for (std::string_view transformer : transformers) { - PW_TRANSFORM_ID transform_id = PWTransformer::getIDForString(transformer); - if (transform_id == PWT_INVALID) { - throw ddwaf::parsing_error("invalid transformer " + std::string(transformer)); - } + std::vector rule_transformers; + auto source = ddwaf::condition::data_source::values; + auto transformers = at(rule, "transformers", {}); + for (const auto &transformer_param : transformers) { + auto transformer = static_cast(transformer_param); + PW_TRANSFORM_ID transform_id = PWTransformer::getIDForString(transformer); + if (transform_id == PWT_INVALID) { + throw ddwaf::parsing_error("invalid transformer " + std::string(transformer)); + } - if (transform_id == PWT_KEYS_ONLY) { - if (!rule_transformers.empty()) { - DDWAF_WARN("keys_only transformer should be the first one " - "in the list, all transformers will be applied to " - "keys and not values"); - } - source = ddwaf::condition::data_source::keys; - } else { - rule_transformers.push_back(transform_id); + if (transform_id == PWT_KEYS_ONLY) { + if (!rule_transformers.empty()) { + DDWAF_WARN("keys_only transformer should be the first one " + "in the list, all transformers will be applied to " + "keys and not values"); } + source = ddwaf::condition::data_source::keys; + } else { + rule_transformers.push_back(transform_id); } + } - std::vector conditions; - auto conditions_array = at(rule, "conditions"); - conditions.reserve(conditions_array.size()); + std::vector conditions; + auto conditions_array = at(rule, "conditions"); + conditions.reserve(conditions_array.size()); - for (parameter::map cond : conditions_array) { - conditions.push_back( - parse_condition(cond, rs.dispatcher, mb, cfg, source, rule_transformers)); + for (const auto &cond_param : conditions_array) { + auto cond = static_cast(cond_param); + conditions.push_back(parse_rule_condition( + cond, target_manifest, rule_data_ids, source, rule_transformers, limits)); + } + + std::unordered_map tags; + for (auto &[key, value] : at(rule, "tags")) { + try { + tags.emplace(key, std::string(value)); + } catch (const bad_cast &e) { + throw invalid_type(std::string(key), e); } + } - auto tags = at(rule, "tags"); - auto rule_ptr = std::make_shared(std::string(id), - at(rule, "name"), at(tags, "type"), - at(tags, "category", ""), std::move(conditions), - at>(rule, "on_match", {}), at(rule, "enabled", true)); - - rs.insert_rule(rule_ptr); - info.add_loaded(); - } catch (const std::exception &e) { - DDWAF_WARN("failed to parse rule '%s': %s", id.c_str(), e.what()); - info.insert_error(id, e.what()); + if (tags.find("type") == tags.end()) { + throw ddwaf::parsing_error("missing key 'type'"); } + + return {at(rule, "enabled", true), at(rule, "name"), std::move(tags), + std::move(conditions), at>(rule, "on_match", {})}; } -std::set parse_rules_target(parameter::map &target, ddwaf::ruleset &rs) +rule_target_spec parse_rules_target(const parameter::map &target) { auto rule_id = at(target, "rule_id", {}); if (!rule_id.empty()) { - const auto &rule_it = rs.rules.find(rule_id); - if (rule_it != rs.rules.end()) { - return {rule_it->second}; - } - return {}; + return {target_type::id, std::move(rule_id), {}}; } - auto tags = at(target, "tags", {}); - if (tags.empty()) { - throw ddwaf::parsing_error("empty rules_target tags"); + auto tag_map = at(target, "tags", {}); + if (tag_map.empty()) { + throw ddwaf::parsing_error("empty rules_target"); } - std::string type; - std::string category; - for (auto &[tag, value] : tags) { - if (tag == "type") { - type = std::string(value); - } else if (tag == "category") { - category = std::string(value); - } else { - DDWAF_WARN("Unknown tag %s in rules_target", tag.data()); - } + std::unordered_map tags; + for (auto &[key, value] : tag_map) { tags.emplace(key, value); } + + return {target_type::tags, {}, std::move(tags)}; +} + +std::pair parse_override(const parameter::map &node) +{ + // Note that ID is a duplicate field and will be deprecated at some point + override_spec current; + + auto it = node.find("enabled"); + if (it != node.end()) { + current.enabled = static_cast(it->second); + } + + it = node.find("on_match"); + if (it != node.end()) { + auto actions = static_cast>(it->second); + current.actions = std::move(actions); } - if (!type.empty()) { - if (!category.empty()) { - return rs.get_rules_by_type_and_category(type, category); + target_type type = target_type::none; + + auto rules_target_array = at(node, "rules_target", {}); + if (!rules_target_array.empty()) { + current.targets.reserve(rules_target_array.size()); + + for (const auto &target : rules_target_array) { + auto target_spec = parse_rules_target(static_cast(target)); + if (type == target_type::none) { + type = target_spec.type; + } else if (type != target_spec.type) { + throw ddwaf::parsing_error("rule_override targets rules and tags"); + } + + current.targets.emplace_back(std::move(target_spec)); } - return rs.get_rules_by_type(type); + } else { + // Since the rules_target array is empty, the ID is mandatory + current.targets.emplace_back( + rule_target_spec{target_type::id, at(node, "id"), {}}); + type = target_type::id; } - if (!category.empty()) { - return rs.get_rules_by_category(category); + if (!current.actions.has_value() && !current.enabled.has_value()) { + throw ddwaf::parsing_error("rules_override without side-effects"); } - throw ddwaf::parsing_error("no supported tags in rules_target"); + return {current, type}; } -void parse_exclusion_filter( - parameter::map &filter, manifest_builder &mb, ddwaf::ruleset &rs, ddwaf::config &cfg) +condition::ptr parse_filter_condition( + const parameter::map &root, manifest &target_manifest, const object_limits &limits) { - auto id = at(filter, "id"); - if (rs.rule_filters.find(id) != rs.rule_filters.end() || - rs.input_filters.find(id) != rs.input_filters.end()) { - DDWAF_WARN("duplicate exclusion filter %s", id.c_str()); - return; + auto operation = at(root, "operator"); + auto params = at(root, "parameters"); + + auto [rule_data_id, processor] = parse_processor(operation, params); + if (!rule_data_id.empty()) { + throw ddwaf::parsing_error("filter conditions don't support dynamic data"); + } + + std::vector targets; + auto inputs = at(params, "inputs"); + if (inputs.empty()) { + throw ddwaf::parsing_error("empty inputs"); } - try { - // Check for conditions first - std::vector conditions; - auto conditions_array = at(filter, "conditions", {}); - if (!conditions_array.empty()) { - conditions.reserve(conditions_array.size()); + for (const auto &input_param : inputs) { + auto input = static_cast(input_param); - for (parameter::map cond : conditions_array) { - conditions.push_back(parse_condition(cond, rs.dispatcher, mb, cfg)); - } + auto address = at(input, "address"); + if (address.empty()) { + throw ddwaf::parsing_error("empty address"); } - std::set rules_target; - auto rules_target_array = at(filter, "rules_target", {}); - if (rules_target_array.empty()) { - for (const auto &[id, rule] : rs.rules) { rules_target.emplace(rule); } - } else { - for (parameter::map target : rules_target_array) { - auto rules_subset = parse_rules_target(target, rs); - rules_target.merge(rules_subset); + auto key_path = at>(input, "key_path", {}); + for (const auto &path : key_path) { + if (path.empty()) { + throw ddwaf::parsing_error("empty key_path"); } } - if (filter.find("inputs") == filter.end()) { - // Rule filter - if (conditions.empty() && rules_target.empty()) { - throw ddwaf::parsing_error("empty exclusion filter"); - } + condition::target_type target; + target.root = target_manifest.insert(address); + target.name = address; + target.key_path = std::move(key_path); - auto filter_ptr = std::make_shared( - std::move(id), std::move(conditions), std::move(rules_target)); - rs.rule_filters.emplace(filter_ptr->get_id(), filter_ptr); - } else { - // Input filter - std::unordered_set input_targets; - exclusion::object_filter obj_filter{cfg.limits}; - auto inputs_array = at(filter, "inputs"); - for (parameter::map input_map : inputs_array) { - auto address = at(input_map, "address"); - - auto optional_target = mb.find(address); - if (!optional_target.has_value()) { - // This address isn't used by any rule so we skip it. - throw ddwaf::parsing_error( - "Address " + address + " not used by any existing rule"); - } + targets.emplace_back(target); + } - auto key_path = at>(input_map, "key_path", {}); + return std::make_shared(std::move(targets), std::vector{}, + std::move(processor), std::string{}, limits); +} - obj_filter.insert(*optional_target, key_path); - } +input_filter_spec parse_input_filter( + const parameter::map &filter, manifest &target_manifest, const object_limits &limits) - auto filter_ptr = std::make_shared(std::move(id), - std::move(conditions), std::move(rules_target), std::move(obj_filter)); - rs.input_filters.emplace(filter_ptr->get_id(), filter_ptr); +{ + // Check for conditions first + std::vector conditions; + auto conditions_array = at(filter, "conditions", {}); + if (!conditions_array.empty()) { + conditions.reserve(conditions_array.size()); + + for (const auto &cond : conditions_array) { + conditions.push_back( + parse_filter_condition(static_cast(cond), target_manifest, limits)); } - } catch (const std::exception &e) { - DDWAF_WARN("failed to parse filter '%s': %s", id.c_str(), e.what()); } -} -} // namespace + std::vector rules_target; + auto rules_target_array = at(filter, "rules_target", {}); + if (!rules_target_array.empty()) { + rules_target.reserve(rules_target_array.size()); + + for (const auto &target : rules_target_array) { + rules_target.emplace_back(parse_rules_target(static_cast(target))); + } + } + + std::unordered_set input_targets; + auto obj_filter = std::make_shared(limits); + auto inputs_array = at(filter, "inputs"); + + // TODO: add empty method to object filter and check after + if (conditions.empty() && inputs_array.empty() && rules_target.empty()) { + throw ddwaf::parsing_error("empty exclusion filter"); + } + + for (const auto &input_param : inputs_array) { + auto input_map = static_cast(input_param); + auto address = at(input_map, "address"); + + auto optional_target = target_manifest.find(address); + if (!optional_target.has_value()) { + // This address isn't used by any rule so we skip it. + DDWAF_DEBUG("Address %s not used by any existing rule", address.c_str()); + continue; + } + + auto key_path = at>(input_map, "key_path", {}); + + obj_filter->insert(*optional_target, key_path); + } + + return {std::move(conditions), std::move(obj_filter), std::move(rules_target)}; +} -void parse(parameter::map &ruleset, ruleset_info &info, ddwaf::ruleset &rs, ddwaf::config &cfg) +rule_filter_spec parse_rule_filter( + const parameter::map &filter, manifest &target_manifest, const object_limits &limits) { - auto metadata = at(ruleset, "metadata", {}); - auto rules_version = metadata.find("rules_version"); - if (rules_version != metadata.end()) { - info.set_version(rules_version->second); + // Check for conditions first + std::vector conditions; + auto conditions_array = at(filter, "conditions", {}); + if (!conditions_array.empty()) { + conditions.reserve(conditions_array.size()); + + for (const auto &cond : conditions_array) { + conditions.push_back( + parse_filter_condition(static_cast(cond), target_manifest, limits)); + } + } + + std::vector rules_target; + auto rules_target_array = at(filter, "rules_target", {}); + if (!rules_target_array.empty()) { + rules_target.reserve(rules_target_array.size()); + + for (const auto &target : rules_target_array) { + rules_target.emplace_back(parse_rules_target(static_cast(target))); + } } - auto rules_array = at(ruleset, "rules"); - rs.rules.reserve(rules_array.size()); + if (conditions.empty() && rules_target.empty()) { + throw ddwaf::parsing_error("empty exclusion filter"); + } + + return {std::move(conditions), std::move(rules_target)}; +} + +} // namespace - ddwaf::manifest_builder mb; - for (parameter::map rule : rules_array) { +rule_spec_container parse_rules(parameter::vector &rule_array, ddwaf::ruleset_info &info, + manifest &target_manifest, std::unordered_map &rule_data_ids, + const object_limits &limits) +{ + rule_spec_container rules; + for (const auto &rule_param : rule_array) { + auto rule_map = static_cast(rule_param); + std::string id; try { - parse_rule(rule, info, mb, rs, cfg); + id = at(rule_map, "id"); + if (rules.find(id) != rules.end()) { + DDWAF_WARN("duplicate rule %s", id.c_str()); + info.insert_error(id, "duplicate rule"); + continue; + } + + auto rule = parse_rule(rule_map, target_manifest, rule_data_ids, limits); + rules.emplace(std::move(id), std::move(rule)); + info.add_loaded(); } catch (const std::exception &e) { - DDWAF_WARN("%s", e.what()); - info.add_failed(); + if (!id.empty()) { + DDWAF_WARN("failed to parse rule '%s': %s", id.c_str(), e.what()); + info.insert_error(id, e.what()); + } else { + DDWAF_WARN("failed to parse rule: %s", e.what()); + info.add_failed(); + } } } - if (rs.rules.empty()) { - throw ddwaf::parsing_error("no valid rules found"); + return rules; +} + +rule_data_container parse_rule_data( + parameter::vector &rule_data, std::unordered_map &rule_data_ids) +{ + rule_data_container processors; + for (ddwaf::parameter object : rule_data) { + std::string id; + try { + auto entry = static_cast(object); + + id = at(entry, "id"); + + auto type = at(entry, "type"); + auto data = at(entry, "data"); + + std::string_view operation; + auto it = rule_data_ids.find(id); + if (it == rule_data_ids.end()) { + // Infer processor from data type + if (type == "ip_with_expiration") { + operation = "ip_match"; + } else if (type == "data_with_expiration") { + operation = "exact_match"; + } else { + DDWAF_DEBUG("Failed to process rule idata id '%s", id.c_str()); + } + } else { + operation = it->second; + } + + rule_processor::base::ptr processor; + if (operation == "ip_match") { + using rule_data_type = rule_processor::ip_match::rule_data_type; + auto parsed_data = parser::parse_rule_data(type, data); + processor = std::make_shared(parsed_data); + } else if (operation == "exact_match") { + using rule_data_type = rule_processor::exact_match::rule_data_type; + auto parsed_data = parser::parse_rule_data(type, data); + processor = std::make_shared(parsed_data); + } else { + DDWAF_WARN("Processor %.*s doesn't support dynamic rule data", + static_cast(operation.length()), operation.data()); + continue; + } + + processors.emplace(std::move(id), std::move(processor)); + } catch (const ddwaf::exception &e) { + DDWAF_ERROR("Failed to parse data id '%s': %s", + (!id.empty() ? id.c_str() : "(unknown)"), e.what()); + } } - auto filters_array = at(ruleset, "exclusions", {}); - for (parameter::map filter : filters_array) { + return processors; +} + +override_spec_container parse_overrides(parameter::vector &override_array) +{ + override_spec_container overrides; + + for (const auto &node_param : override_array) { + auto node = static_cast(node_param); try { - parse_exclusion_filter(filter, mb, rs, cfg); + auto [spec, type] = parse_override(node); + if (type == target_type::id) { + overrides.by_ids.emplace_back(std::move(spec)); + } else if (type == target_type::tags) { + overrides.by_tags.emplace_back(std::move(spec)); + } else { + DDWAF_WARN("override with no targets"); + } } catch (const std::exception &e) { - DDWAF_WARN("%s", e.what()); - info.add_failed(); + DDWAF_WARN("failed to parse rule_override: %s", e.what()); } } - rs.manifest = mb.build_manifest(); + return overrides; +} - auto data_array = at(ruleset, "rules_data", {}); - if (!data_array.empty()) { - rs.dispatcher.dispatch(data_array); +filter_spec_container parse_filters( + parameter::vector &filter_array, manifest &target_manifest, const object_limits &limits) +{ + filter_spec_container filters; + for (const auto &node_param : filter_array) { + auto node = static_cast(node_param); + std::string id; + try { + id = at(node, "id"); + if (filters.ids.find(id) != filters.ids.end()) { + DDWAF_WARN("duplicate filter: %s", id.c_str()); + continue; + } + + if (node.find("inputs") != node.end()) { + auto filter = parse_input_filter(node, target_manifest, limits); + filters.ids.emplace(id); + filters.input_filters.emplace(std::move(id), std::move(filter)); + } else { + auto filter = parse_rule_filter(node, target_manifest, limits); + filters.ids.emplace(id); + filters.rule_filters.emplace(std::move(id), std::move(filter)); + } + } catch (const std::exception &e) { + if (!id.empty()) { + DDWAF_WARN("failed to parse filter '%s': %s", id.c_str(), e.what()); + } else { + DDWAF_WARN("failed to parse filter: %s", e.what()); + } + } } - DDWAF_DEBUG("Loaded %zu rules out of %zu available in the ruleset", rs.rules.size(), - rules_array.size()); + return filters; } } // namespace ddwaf::parser::v2 diff --git a/src/parser/rule_data_parser.cpp b/src/parser/rule_data_parser.cpp index b176629ca..2bc04226b 100644 --- a/src/parser/rule_data_parser.cpp +++ b/src/parser/rule_data_parser.cpp @@ -21,8 +21,9 @@ data_with_expiration parse_rule_data(std::string_view type data_with_expiration data; data.reserve(input.nbEntries); - parameter::vector array = input; - for (parameter::map values : array) { + auto array = static_cast(input); + for (const auto &values_param : array) { + auto values = static_cast(values_param); data.emplace_back( at(values, "value"), at(values, "expiration", 0)); } diff --git a/src/parser/specification.hpp b/src/parser/specification.hpp new file mode 100644 index 000000000..14ebb0b17 --- /dev/null +++ b/src/parser/specification.hpp @@ -0,0 +1,84 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#pragma once + +#include +#include +#include +#include + +#include + +namespace ddwaf::parser { + +struct rule_spec { + bool enabled; + std::string name; + std::unordered_map tags; + std::vector conditions; + std::vector actions; +}; + +enum class target_type { none, id, tags }; + +struct rule_target_spec { + target_type type; + std::string rule_id; + std::unordered_map tags; +}; + +struct override_spec { + std::optional enabled; + std::optional> actions; + std::vector targets; +}; + +// Filter conditions don't need to be regenerated, so we don't need to use +// the condition_spec +struct rule_filter_spec { + std::vector conditions; + std::vector targets; +}; + +struct input_filter_spec { + std::vector conditions; + std::shared_ptr filter; + std::vector targets; +}; + +// Containers +using rule_spec_container = std::unordered_map; +using rule_data_container = std::unordered_map; + +struct override_spec_container { + [[nodiscard]] bool empty() const { return by_ids.empty() && by_tags.empty(); } + void clear() + { + by_ids.clear(); + by_tags.clear(); + } + // The distinction is only necessary due to the restriction that + // overrides by ID are to be considered a priority over overrides by tags + std::vector by_ids; + std::vector by_tags; +}; + +struct filter_spec_container { + [[nodiscard]] bool empty() const { return rule_filters.empty() && input_filters.empty(); } + + void clear() + { + rule_filters.clear(); + input_filters.clear(); + } + + std::unordered_set ids; + std::unordered_map rule_filters; + std::unordered_map input_filters; +}; + +} // namespace ddwaf::parser diff --git a/src/rule.cpp b/src/rule.cpp index c3088240e..7a8f6073b 100644 --- a/src/rule.cpp +++ b/src/rule.cpp @@ -15,20 +15,15 @@ namespace ddwaf { -rule::rule(std::string &&id_, std::string &&name_, std::string &&type_, std::string &&category_, - std::vector &&conditions_, std::vector &&actions_, bool enabled_) - : enabled(enabled_), id(std::move(id_)), name(std::move(name_)), type(std::move(type_)), - category(std::move(category_)), conditions(std::move(conditions_)), - actions(std::move(actions_)) -{ - for (auto &cond : conditions) { - const auto &cond_targets = cond->get_targets(); - targets.insert(cond_targets.begin(), cond_targets.end()); - } -} +rule::rule(std::string id_, std::string name_, std::unordered_map tags_, + std::vector conditions_, std::vector actions_, bool enabled_) + : enabled(enabled_), id(std::move(id_)), name(std::move(name_)), tags(std::move(tags_)), + conditions(std::move(conditions_)), actions(std::move(actions_)) +{} -std::optional rule::match(const object_store &store, const ddwaf::manifest &manifest, - cache_type &cache, const std::unordered_set &objects_excluded, +std::optional rule::match(const object_store &store, cache_type &cache, + const std::unordered_set &objects_excluded, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const { // An event was already produced, so we skip the rule @@ -49,7 +44,8 @@ std::optional rule::match(const object_store &store, const ddwaf::manifes cached_result = it; } - auto opt_match = cond->match(store, manifest, objects_excluded, run_on_new, deadline); + auto opt_match = + cond->match(store, objects_excluded, run_on_new, dynamic_processors, deadline); if (!opt_match.has_value()) { cached_result->second = false; return std::nullopt; @@ -62,8 +58,8 @@ std::optional rule::match(const object_store &store, const ddwaf::manifes cache.event.id = id; cache.event.name = name; - cache.event.type = type; - cache.event.category = category; + cache.event.type = get_tag("type"); + cache.event.category = get_tag("category"); cache.event.actions.reserve(actions.size()); for (const auto &action : actions) { cache.event.actions.push_back(action); } diff --git a/src/rule.hpp b/src/rule.hpp index 591fa9eb8..57e5dac37 100644 --- a/src/rule.hpp +++ b/src/rule.hpp @@ -17,8 +17,8 @@ #include #include #include -#include #include +#include #include namespace ddwaf { @@ -36,53 +36,51 @@ class rule { // TODO: make fields protected, add getters, follow conventions, add cache // move condition matching from context. - rule(std::string &&id_, std::string &&name_, std::string &&type_, std::string &&category_, - std::vector &&conditions_, std::vector &&actions_ = {}, + rule(std::string id_, std::string name_, std::unordered_map tags_, + std::vector conditions_, std::vector actions_ = {}, bool enabled_ = true); rule(const rule &) = delete; rule &operator=(const rule &) = delete; - // Atomics aren't movable so the default move constructor and move - // assignment operator can't be used. With this constructor and operator - // any relevant atomic member does not behave as such. rule(rule &&rhs) noexcept - : enabled(rhs.enabled.load(std::memory_order_relaxed)), id(std::move(rhs.id)), - name(std::move(rhs.name)), type(std::move(rhs.type)), category(std::move(rhs.category)), - conditions(std::move(rhs.conditions)), targets(std::move(rhs.targets)), + : enabled(rhs.enabled), id(std::move(rhs.id)), name(std::move(rhs.name)), + tags(std::move(rhs.tags)), conditions(std::move(rhs.conditions)), actions(std::move(rhs.actions)) {} rule &operator=(rule &&rhs) noexcept { - enabled = rhs.enabled.load(std::memory_order_relaxed); + enabled = rhs.enabled; id = std::move(rhs.id); name = std::move(rhs.name); - type = std::move(rhs.type); - category = std::move(rhs.category); + tags = std::move(rhs.tags); conditions = std::move(rhs.conditions); - targets = std::move(rhs.targets); actions = std::move(rhs.actions); - return *this; } ~rule() = default; - std::optional match(const object_store &store, const ddwaf::manifest &manifest, - cache_type &cache, const std::unordered_set &objects_excluded, + std::optional match(const object_store &store, cache_type &cache, + const std::unordered_set &objects_excluded, + const std::unordered_map &dynamic_processors, ddwaf::timer &deadline) const; - bool is_enabled() const { return enabled.load(std::memory_order_relaxed); } - void toggle(bool value) { enabled.store(value, std::memory_order_relaxed); } + [[nodiscard]] bool is_enabled() const { return enabled; } + void toggle(bool value) { enabled = value; } + + std::string_view get_tag(const std::string &tag) const + { + auto it = tags.find(tag); + return it == tags.end() ? std::string_view() : it->second; + } - std::atomic enabled{true}; + bool enabled{true}; std::string id; std::string name; - std::string type; - std::string category; + std::unordered_map tags; std::vector conditions; - std::unordered_set targets; std::vector actions; }; diff --git a/src/rule_data_dispatcher.hpp b/src/rule_data_dispatcher.hpp deleted file mode 100644 index af987ebf3..000000000 --- a/src/rule_data_dispatcher.hpp +++ /dev/null @@ -1,138 +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 -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ddwaf::rule_data { - -class dispatcher { -protected: - class type_dispatcher_base { - public: - virtual ~type_dispatcher_base() = default; - virtual void dispatch(std::string_view type, ddwaf::parameter &data) = 0; - }; - - template class type_dispatcher : public type_dispatcher_base { - public: - using constructor_wrapper_function = - std::function(const RuleDataType &)>; - - type_dispatcher() = default; - ~type_dispatcher() override = default; - - void insert(constructor_wrapper_function &&fn, const condition::ptr &cond) - { - functions_.emplace_back(std::move(fn), cond); - } - - void dispatch(std::string_view type, ddwaf::parameter &data) override - { - auto converted_data = parser::parse_rule_data(type, data); - for (auto &[fn, cond] : functions_) { - auto processor = fn(converted_data); - if (processor) { - cond->reset_processor(processor); - } - } - } - - protected: - std::vector> functions_; - }; - -public: - dispatcher() = default; - ~dispatcher() = default; - - dispatcher(const dispatcher &) = delete; - dispatcher &operator=(const dispatcher &) = delete; - - dispatcher(dispatcher &&) = default; - dispatcher &operator=(dispatcher &&) = default; - - template >>, - std::negation>>>>>> - void register_condition(const std::string &id, const condition::ptr &cond) - { - using rule_data_type = typename RuleProcessorType::rule_data_type; - auto it = type_dispatchers_.find(id); - if (it == type_dispatchers_.end()) { - auto [new_it, res] = - type_dispatchers_.emplace(id, std::make_unique>()); - it = new_it; - } - - auto &td = dynamic_cast &>(*it->second.get()); - - td.insert( - [](const rule_data_type &data) { return std::make_shared(data); }, - cond); - } - - void dispatch(const std::string &id, std::string_view type, parameter &data) - { - auto it = type_dispatchers_.find(id); - if (it == type_dispatchers_.end()) { - DDWAF_WARN("Rule data id '%s' has no associated dispatcher", id.c_str()); - return; - } - - it->second->dispatch(type, data); - } - - void dispatch(parameter::vector &input) - { - for (ddwaf::parameter object : input) { - std::string id; - try { - ddwaf::parameter::map entry = object; - - id = parser::at(entry, "id"); - auto type = parser::at(entry, "type"); - - DDWAF_DEBUG("Updating rules with id '%s' and type '%s'", id.c_str(), type.data()); - - auto data = parser::at(entry, "data"); - dispatch(id, type, data); - } catch (const ddwaf::exception &e) { - DDWAF_ERROR("Failed to parse data id '%s': %s", - (!id.empty() ? id.c_str() : "(unknown)"), e.what()); - } - } - } - - const std::vector &get_rule_data_ids() - { - if (!type_dispatchers_.empty() && rule_data_ids_.empty()) { - rule_data_ids_.reserve(type_dispatchers_.size()); - for (auto &[key, value] : type_dispatchers_) { - rule_data_ids_.emplace_back(key.c_str()); - } - } - - return rule_data_ids_; - } - -protected: - std::unordered_map> type_dispatchers_; - std::vector rule_data_ids_; -}; -} // namespace ddwaf::rule_data diff --git a/src/rule_processor/base.hpp b/src/rule_processor/base.hpp index 5d0b99009..df5719a4c 100644 --- a/src/rule_processor/base.hpp +++ b/src/rule_processor/base.hpp @@ -19,7 +19,7 @@ namespace ddwaf::rule_processor { class base { public: - using shared = std::shared_ptr; + using ptr = std::shared_ptr; base() = default; virtual ~base() = default; diff --git a/src/ruleset.hpp b/src/ruleset.hpp index 7e0fa59ba..243797a1b 100644 --- a/src/ruleset.hpp +++ b/src/ruleset.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -14,81 +15,54 @@ #include #include #include +#include +#include #include -#include namespace ddwaf { +using rule_tag_map = ddwaf::multi_key_map; + struct ruleset { + using ptr = std::shared_ptr; + void insert_rule(rule::ptr rule) { rules.emplace(rule->id, rule); if (rule->actions.empty()) { - collections[rule->type].insert(rule); + collections[rule->get_tag("type")].insert(rule); } else { - priority_collections[rule->type].insert(rule); - } - rules_by_type[rule->type].emplace(rule); - rules_by_category[rule->category].emplace(rule); - } - - // TODO use unordered sets for these functions - std::set get_rules_by_type(std::string_view type) const - { - auto it = rules_by_type.find(type); - if (it == rules_by_type.end()) { - return {}; + priority_collections[rule->get_tag("type")].insert(rule); } - return it->second; } - std::set get_rules_by_category(std::string_view category) const + void insert_rules(std::unordered_map rules_) { - auto it = rules_by_category.find(category); - if (it == rules_by_category.end()) { - return {}; + rules = std::move(rules_); + + for (const auto &[id, rule] : rules) { + if (rule->actions.empty()) { + collections[rule->get_tag("type")].insert(rule); + } else { + priority_collections[rule->get_tag("type")].insert(rule); + } } - return it->second; } - std::set get_rules_by_type_and_category( - std::string_view type, std::string_view category) const - { - auto type_it = rules_by_type.find(type); - if (type_it == rules_by_type.end()) { - return {}; - } - - auto category_it = rules_by_category.find(category); - if (category_it == rules_by_category.end()) { - return {}; - } - - const std::set &type_set = type_it->second; - const std::set &category_set = category_it->second; - std::set intersection; - - std::set_intersection(type_set.begin(), type_set.end(), category_set.begin(), - category_set.end(), std::inserter(intersection, intersection.begin())); - - return intersection; - } + ddwaf_object_free_fn free_fn{ddwaf_object_free}; + std::shared_ptr event_obfuscator; ddwaf::manifest manifest; std::unordered_map rule_filters; std::unordered_map input_filters; // Rules are ordered by rule.id - std::unordered_map rules; + std::unordered_map rules; + std::unordered_map dynamic_processors; // Both collections are ordered by rule.type std::unordered_map priority_collections; std::unordered_map collections; - - ddwaf::rule_data::dispatcher dispatcher; - - std::unordered_map> rules_by_type; - std::unordered_map> rules_by_category; }; } // namespace ddwaf diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp new file mode 100644 index 000000000..8f03cc34a --- /dev/null +++ b/src/ruleset_builder.cpp @@ -0,0 +1,291 @@ +// 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 "parser/specification.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace ddwaf { + +constexpr ruleset_builder::change_state operator|( + ruleset_builder::change_state lhs, ruleset_builder::change_state rhs) +{ + return static_cast( + static_cast::type>(lhs) | + static_cast::type>(rhs)); +} + +constexpr ruleset_builder::change_state operator&( + ruleset_builder::change_state lhs, ruleset_builder::change_state rhs) +{ + return static_cast( + static_cast::type>(lhs) & + static_cast::type>(rhs)); +} + +namespace { + +std::set target_to_rules(const std::vector &targets, + const std::unordered_map &rules, const rule_tag_map &rules_by_tags) +{ + std::set rule_targets; + if (!targets.empty()) { + for (const auto &target : targets) { + if (target.type == parser::target_type::id) { + auto rule_it = rules.find(target.rule_id); + if (rule_it == rules.end()) { + continue; + } + rule_targets.emplace(rule_it->second); + } else if (target.type == parser::target_type::tags) { + auto current_targets = rules_by_tags.multifind(target.tags); + rule_targets.merge(current_targets); + } + } + } else { + // An empty rules target applies to all rules + for (const auto &[id, rule] : rules) { rule_targets.emplace(rule); } + } + return rule_targets; +} + +} // namespace + +std::shared_ptr ruleset_builder::build(parameter::map &root, ruleset_info &info) +{ + // Load new rules, overrides and exclusions + auto state = load(root, info); + + if (state == change_state::none) { + return {}; + } + + constexpr static change_state rule_update = change_state::rules | change_state::overrides; + constexpr static change_state filters_update = rule_update | change_state::filters; + constexpr static change_state manifest_update = change_state::rules | change_state::filters; + + // When a configuration with 'rules', 'rules_data' or 'rules_override' is + // received, we need to regenerate the ruleset from the base rules as we + // want to ensure that there are no side-effects on running contexts. + if ((state & rule_update) != change_state::none) { + final_rules_.clear(); + rules_by_tags_.clear(); + targets_from_rules_.clear(); + + // Initially, new rules are generated from their spec + for (const auto &[id, spec] : base_rules_) { + for (const auto &cond : spec.conditions) { + for (const auto &target : cond->get_targets()) { + targets_from_rules_.emplace(target.root); + } + } + + auto rule_ptr = std::make_shared( + id, spec.name, spec.tags, spec.conditions, spec.actions, spec.enabled); + + // The string_view should be owned by the rule_ptr + final_rules_.emplace(rule_ptr->id, rule_ptr); + rules_by_tags_.insert(rule_ptr->tags, rule_ptr); + } + + // Old or new overrides are applied on the new rules + std::unordered_set overridden_rules; + for (const auto &ovrd : overrides_.by_ids) { + // Overrides by ID + auto rule_targets = target_to_rules(ovrd.targets, final_rules_, rules_by_tags_); + for (const auto &rule_ptr : rule_targets) { + if (overridden_rules.find(rule_ptr.get()) != overridden_rules.end()) { + continue; + } + + if (ovrd.enabled.has_value()) { + rule_ptr->toggle(*ovrd.enabled); + } + + if (ovrd.actions.has_value()) { + rule_ptr->actions = *ovrd.actions; + } + + overridden_rules.emplace(rule_ptr.get()); + } + } + + // Apply overrides by tag + for (const auto &ovrd : overrides_.by_tags) { + auto rule_targets = target_to_rules(ovrd.targets, final_rules_, rules_by_tags_); + for (const auto &rule_ptr : rule_targets) { + // If a rule has been overridden by ID, it shouldn't be overridden + // by tag. + if (overridden_rules.find(rule_ptr.get()) != overridden_rules.end()) { + continue; + } + + if (ovrd.enabled.has_value()) { + rule_ptr->toggle(*ovrd.enabled); + } + + if (ovrd.actions.has_value()) { + rule_ptr->actions = *ovrd.actions; + } + } + } + } + + // Generate exclusion filters targetting final_rules_ + if ((state & filters_update) != change_state::none) { + rule_filters_.clear(); + input_filters_.clear(); + targets_from_filters_.clear(); + + // First generate rule filters + for (const auto &[id, filter] : exclusions_.rule_filters) { + auto rule_targets = target_to_rules(filter.targets, final_rules_, rules_by_tags_); + auto filter_ptr = std::make_shared( + id, filter.conditions, std::move(rule_targets)); + rule_filters_.emplace(filter_ptr->get_id(), filter_ptr); + + for (const auto &cond : filter.conditions) { + for (const auto &target : cond->get_targets()) { + targets_from_filters_.emplace(target.root); + } + } + } + + // Finally input filters + for (auto &[id, filter] : exclusions_.input_filters) { + auto rule_targets = target_to_rules(filter.targets, final_rules_, rules_by_tags_); + auto filter_ptr = std::make_shared( + id, filter.conditions, std::move(rule_targets), filter.filter); + input_filters_.emplace(filter_ptr->get_id(), filter_ptr); + + for (const auto &target : filter.filter->get_targets()) { + targets_from_filters_.emplace(target); + } + + for (const auto &cond : filter.conditions) { + for (const auto &target : cond->get_targets()) { + targets_from_filters_.emplace(target.root); + } + } + } + } + + if ((state & manifest_update) != change_state::none) { + // Remove unnecessary targets using all the targets contained within + // rule conditions, filter conditions and object filters + std::unordered_set all_targets; + all_targets.insert(targets_from_rules_.begin(), targets_from_rules_.end()); + all_targets.insert(targets_from_filters_.begin(), targets_from_filters_.end()); + + target_manifest_.remove_unused(all_targets); + } + + auto rs = std::make_shared(); + rs->manifest = target_manifest_; + rs->insert_rules(final_rules_); + rs->dynamic_processors = dynamic_processors_; + rs->rule_filters = rule_filters_; + rs->input_filters = input_filters_; + rs->free_fn = free_fn_; + rs->event_obfuscator = event_obfuscator_; + + return rs; +} + +ruleset_builder::change_state ruleset_builder::load(parameter::map &root, ruleset_info &info) +{ + change_state state = change_state::none; + + auto metadata = parser::at(root, "metadata", {}); + auto rules_version = metadata.find("rules_version"); + if (rules_version != metadata.end()) { + info.set_version(static_cast(rules_version->second)); + } + + auto it = root.find("rules"); + if (it != root.end()) { + decltype(rule_data_ids_) rule_data_ids; + + auto rules = static_cast(it->second); + auto new_base_rules = + parser::v2::parse_rules(rules, info, target_manifest_, rule_data_ids, limits_); + + if (new_base_rules.empty()) { + throw ddwaf::parsing_error("no valid rules found"); + } + + // Upon reaching this stage, we know our base ruleset is valid + base_rules_ = std::move(new_base_rules); + rule_data_ids_ = std::move(rule_data_ids); + state = state | change_state::rules; + } + + it = root.find("rules_data"); + if (it != root.end()) { + auto rules_data = static_cast(it->second); + if (!rules_data.empty()) { + auto new_processors = parser::v2::parse_rule_data(rules_data, rule_data_ids_); + if (new_processors.empty()) { + // The rules_data array might have unrelated IDs, so we need + // to consider "no valid IDs" as an empty rules_data + dynamic_processors_.clear(); + state = state | change_state::data; + } else { + dynamic_processors_ = std::move(new_processors); + state = state | change_state::data; + } + } else { + dynamic_processors_.clear(); + state = state | change_state::data; + } + } + + it = root.find("rules_override"); + if (it != root.end()) { + auto overrides = static_cast(it->second); + if (!overrides.empty()) { + auto new_overrides = parser::v2::parse_overrides(overrides); + if (new_overrides.empty()) { + // We can continue whilst ignoring the lack of overrides + DDWAF_WARN("No valid overrides provided"); + } else { + overrides_ = std::move(new_overrides); + state = state | change_state::overrides; + } + } else { + overrides_.clear(); + state = state | change_state::overrides; + } + } + + it = root.find("exclusions"); + if (it != root.end()) { + auto exclusions = static_cast(it->second); + if (!exclusions.empty()) { + auto new_exclusions = parser::v2::parse_filters(exclusions, target_manifest_, limits_); + + if (new_exclusions.empty()) { + // We can continue whilst ignoring the lack of exclusions + DDWAF_WARN("No valid exclusion filters provided"); + } else { + exclusions_ = std::move(new_exclusions); + state = state | change_state::filters; + } + } else { + exclusions_.clear(); + state = state | change_state::filters; + } + } + + return state; +} + +} // namespace ddwaf diff --git a/src/ruleset_builder.hpp b/src/ruleset_builder.hpp new file mode 100644 index 000000000..6bf1d1bd1 --- /dev/null +++ b/src/ruleset_builder.hpp @@ -0,0 +1,105 @@ +// 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 "parser/specification.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ddwaf { + +class ruleset_builder { +public: + using ptr = std::shared_ptr; + + ruleset_builder(object_limits limits, ddwaf_object_free_fn free_fn, + std::shared_ptr event_obfuscator) + : limits_(limits), free_fn_(free_fn), event_obfuscator_(std::move(event_obfuscator)) + {} + + ~ruleset_builder() = default; + ruleset_builder(ruleset_builder &&) = default; + ruleset_builder(const ruleset_builder &) = delete; + ruleset_builder &operator=(ruleset_builder &&) = delete; + ruleset_builder &operator=(const ruleset_builder &) = delete; + + std::shared_ptr build(parameter root_map, ruleset_info &info) + { + auto root = static_cast(root_map); + return build(root, info); + } + + std::shared_ptr build(parameter::map &root, ruleset_info &info); + +protected: + enum class change_state : uint32_t { + none = 0, + rules = 1, + overrides = 2, + filters = 4, + data = 8 + }; + + friend constexpr change_state operator|(change_state lhs, change_state rhs); + friend constexpr change_state operator&(change_state lhs, change_state rhs); + + change_state load(parameter::map &root, ruleset_info &info); + + // These members are obtained through ddwaf_config and are persistent across + // all updates. + const object_limits limits_; + const ddwaf_object_free_fn free_fn_; + std::shared_ptr event_obfuscator_; + + // The same manifest is used across updates, so we need to ensure that + // unused targets are regularly cleaned up. + manifest target_manifest_; + // Map representing rule data IDs to processor type, this is obtained + // from parsing the ruleset ('rules' key). + std::unordered_map rule_data_ids_; + + // These contain the specification of each main component obtained directly + // from the parser. These are only modified on update, if the relevant key + // is present and valid, otherwise they aren't be updated. + // Note that in the case of dynamic_processors, overrides and exclusions + // we allow an empty key as a way to revert or remove the contents of the + // relevant feature. + + // Obtained from 'rules', can't be empty + parser::rule_spec_container base_rules_; + // Obtained from 'rules_data', depends on base_rules_ + parser::rule_data_container dynamic_processors_; + // Obtained from 'rules_override' + parser::override_spec_container overrides_; + // Obtained from 'exclusions' + parser::filter_spec_container exclusions_; + + // These are the contents of the latest generated ruleset + + // Rules + std::unordered_map final_rules_; + // An mkmap organising rules by their tags, used for overrides and exclusion filters + rule_tag_map rules_by_tags_; + // The list of tagets used by the rules in final_rules_, used for manifest cleanup + std::unordered_set targets_from_rules_; + + // Filters + std::unordered_map rule_filters_; + std::unordered_map input_filters_; + // The list of targets used by rule_filters_, input_filters_ and their internal + // object filters, used for manifest cleanup + std::unordered_set targets_from_filters_; +}; + +} // namespace ddwaf diff --git a/src/type_traits.hpp b/src/type_traits.hpp new file mode 100644 index 000000000..ed569a13c --- /dev/null +++ b/src/type_traits.hpp @@ -0,0 +1,17 @@ +// 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 + +namespace ddwaf { + +// https://stackoverflow.com/questions/43992510/enable-if-to-check-if-value-type-of-iterator-is-a-pair +template struct is_pair : std::false_type {}; +template struct is_pair> : std::true_type {}; + +} // namespace ddwaf diff --git a/src/waf.cpp b/src/waf.cpp deleted file mode 100644 index 87e3d441a..000000000 --- a/src/waf.cpp +++ /dev/null @@ -1,100 +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 "clock.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; - -namespace ddwaf { - -namespace { -obfuscator obfuscator_from_config(const ddwaf_config *config) -{ - std::string_view key_regex; - std::string_view value_regex; - - if (config != nullptr) { - if (config->obfuscator.key_regex != nullptr) { - key_regex = config->obfuscator.key_regex; - } - - if (config->obfuscator.value_regex != nullptr) { - value_regex = config->obfuscator.value_regex; - } - } - - return obfuscator(key_regex, value_regex); -} - -ddwaf::object_limits limits_from_config(const ddwaf_config *config) -{ - ddwaf::object_limits limits; - - if (config != nullptr) { - if (config->limits.max_container_size != 0) { - limits.max_container_size = config->limits.max_container_size; - } - - if (config->limits.max_container_depth != 0) { - limits.max_container_depth = config->limits.max_container_depth; - } - - if (config->limits.max_string_length != 0) { - limits.max_string_length = config->limits.max_string_length; - } - } - - return limits; -} - -} // namespace - -waf::ptr waf::from_config( - const ddwaf_object &ruleset, const ddwaf_config *config, ddwaf::ruleset_info &info) -{ - try { - ddwaf::config cfg{ - limits_from_config(config), - obfuscator_from_config(config), - config != nullptr ? config->free_fn : ddwaf_object_free, - }; - - ddwaf::ruleset rs; - parser::parse(ruleset, info, rs, cfg); - return std::shared_ptr(new waf(std::move(rs), std::move(cfg))); - } catch (const std::exception &e) { - DDWAF_ERROR("%s", e.what()); - } - - return nullptr; -} - -void waf::toggle_rules(ddwaf::parameter::map &&input) -{ - for (auto &[key, value] : input) { - auto it = ruleset_.rules.find(std::string(key)); - - if (it == ruleset_.rules.end()) { - DDWAF_WARN("Attempting to toggle an unknown rule %s", key.data()); - continue; - } - - it->second->toggle(value); - } -} - -} // namespace ddwaf diff --git a/src/waf.hpp b/src/waf.hpp index b17a816fc..f6eb4e087 100644 --- a/src/waf.hpp +++ b/src/waf.hpp @@ -5,45 +5,76 @@ // Copyright 2021 Datadog, Inc. #pragma once +#include "ddwaf.h" +#include "parser/parser.hpp" #include #include #include #include +#include #include #include #include namespace ddwaf { -class waf : public std::enable_shared_from_this { +class waf { public: - using ptr = std::shared_ptr; + waf(ddwaf::parameter input, ddwaf::ruleset_info &info, ddwaf::object_limits limits, + ddwaf_object_free_fn free_fn, std::shared_ptr event_obfuscator) + { + auto input_map = static_cast(input); - static waf::ptr from_config( - const ddwaf_object &rules, const ddwaf_config *config, ddwaf::ruleset_info &info); + unsigned version = 2; - ddwaf::context create_context() { return {ruleset_, config_, shared_from_this()}; } + auto it = input_map.find("version"); + if (it != input_map.end()) { + try { + version = parser::parse_schema_version(input_map); + } catch (const std::exception &e) { + DDWAF_DEBUG("Failed to parse version (defaulting to 2): %s", e.what()); + } + } - void update_rule_data(ddwaf::parameter::vector &&input) { ruleset_.dispatcher.dispatch(input); } + // Prevent combining version 1 of the ruleset and the builder + if (version == 1) { + ddwaf::ruleset rs; + rs.free_fn = free_fn; + rs.event_obfuscator = event_obfuscator; + parser::v1::parse(input_map, info, rs, limits); + ruleset_ = std::make_shared(std::move(rs)); + return; + } - void toggle_rules(ddwaf::parameter::map &&input); + builder_ = std::make_shared(limits, free_fn, std::move(event_obfuscator)); + ruleset_ = builder_->build(input, info); + } - const std::vector &get_root_addresses() const + waf *update(ddwaf::parameter input, ddwaf::ruleset_info &info) { - return ruleset_.manifest.get_root_addresses(); + if (builder_) { + auto ruleset = builder_->build(input, info); + if (ruleset) { + return new waf{builder_, std::move(ruleset)}; + } + } + return nullptr; } - const std::vector &get_rule_data_ids() + + ddwaf::context create_context() { return context{ruleset_}; } + + [[nodiscard]] const std::vector &get_root_addresses() const { - return ruleset_.dispatcher.get_rule_data_ids(); + return ruleset_->manifest.get_root_addresses(); } protected: - waf(ddwaf::ruleset &&ruleset, ddwaf::config &&config) - : ruleset_(std::move(ruleset)), config_(std::move(config)) + waf(ddwaf::ruleset_builder::ptr builder, ddwaf::ruleset::ptr ruleset) + : builder_(std::move(builder)), ruleset_(std::move(ruleset)) {} - ddwaf::ruleset ruleset_; - ddwaf::config config_; + ddwaf::ruleset_builder::ptr builder_; + ddwaf::ruleset::ptr ruleset_; }; } // namespace ddwaf diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 19b975ae0..93a506205 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,16 +18,15 @@ if(NOT STDLIB_MAP_RECURSIVE) target_compile_definitions(testPowerWAF PRIVATE HAS_NONRECURSIVE_UNORDERED_MAP) endif() +set(COVERAGE_LINK_FLAGS "") if(NOT MSVC) if(LIBDDWAF_TEST_COVERAGE) set_target_properties(testPowerWAF PROPERTIES COMPILE_FLAGS "-ggdb --coverage") - set(COVERAGE_LINK_FLAGS "--coverage") + set(COVERAGE_LINK_FLAGS "-lgcov --coverage") else() set_target_properties(testPowerWAF PROPERTIES COMPILE_FLAGS "-ggdb") endif() else() - set(COVERAGE_LINK_FLAGS "") - set_target_properties(testPowerWAF PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ".") set_target_properties(testPowerWAF PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ".") set_target_properties(testPowerWAF PROPERTIES RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ".") diff --git a/tests/TestAdditive.cpp b/tests/TestAdditive.cpp index bcc68ee73..b686e2550 100644 --- a/tests/TestAdditive.cpp +++ b/tests/TestAdditive.cpp @@ -8,9 +8,7 @@ void populateManifest(ddwaf::manifest &manifest) { - ddwaf::manifest_builder mb; - for (auto key : {"value", "key", "mixed", "mixed2"}) { mb.insert(key, {}); } - manifest = mb.build_manifest(); + for (const auto *key : {"value", "key", "mixed", "mixed2"}) { manifest.insert(key); } } TEST(TestAdditive, TestMultiCall) diff --git a/tests/TestInterface.cpp b/tests/TestInterface.cpp index 71c2e644a..27a564324 100644 --- a/tests/TestInterface.cpp +++ b/tests/TestInterface.cpp @@ -296,7 +296,7 @@ TEST(FunctionalTests, HandleBad) /*ddwaf_destroy(handle2);*/ /*}*/ -TEST(FunctionalTests, ddwaf_get_version) { EXPECT_STREQ(ddwaf_get_version(), "1.7.0"); } +TEST(FunctionalTests, ddwaf_get_version) { EXPECT_STREQ(ddwaf_get_version(), "1.8.0"); } TEST(FunctionalTests, ddwaf_runNull) { diff --git a/tests/collection_test.cpp b/tests/collection_test.cpp index d3012d9b8..159a7a2bf 100644 --- a/tests/collection_test.cpp +++ b/tests/collection_test.cpp @@ -18,19 +18,20 @@ TYPED_TEST_SUITE(TestCollection, CollectionTypes); TYPED_TEST(TestCollection, SingleRuleMatch) { std::unordered_set seen_actions; - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); TypeParam rule_collection; rule_collection.insert(rule); @@ -47,7 +48,7 @@ TYPED_TEST(TestCollection, SingleRuleMatch) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 1); } @@ -61,7 +62,7 @@ TYPED_TEST(TestCollection, SingleRuleMatch) store.insert(root); std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 0); } @@ -72,10 +73,10 @@ TYPED_TEST(TestCollection, MultipleRuleCachedMatch) { std::unordered_set seen_actions; TypeParam rule_collection; - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -83,29 +84,33 @@ TYPED_TEST(TestCollection, MultipleRuleCachedMatch) std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + auto rule = std::make_shared( - "id1", "name1", "type", "category1", std::move(conditions), std::vector{}); + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); rule_collection.insert(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + auto rule = std::make_shared( - "id2", "name2", "type", "category2", std::move(conditions), std::vector{}); + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); rule_collection.insert(rule); } - auto manifest = mb.build_manifest(); - ddwaf::timer deadline{2s}; ddwaf::object_store store(manifest); auto cache = rule_collection.get_cache(); @@ -119,7 +124,7 @@ TYPED_TEST(TestCollection, MultipleRuleCachedMatch) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 1); } @@ -133,7 +138,7 @@ TYPED_TEST(TestCollection, MultipleRuleCachedMatch) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 0); } @@ -144,10 +149,10 @@ TYPED_TEST(TestCollection, MultipleRuleFailAndMatch) { std::unordered_set seen_actions; TypeParam rule_collection; - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -155,29 +160,31 @@ TYPED_TEST(TestCollection, MultipleRuleFailAndMatch) std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; auto rule = std::make_shared( - "id1", "name1", "type", "category1", std::move(conditions), std::vector{}); + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); rule_collection.insert(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; auto rule = std::make_shared( - "id2", "name2", "type", "category2", std::move(conditions), std::vector{}); + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); rule_collection.insert(rule); } - auto manifest = mb.build_manifest(); - ddwaf::timer deadline{2s}; ddwaf::object_store store(manifest); auto cache = rule_collection.get_cache(); @@ -191,7 +198,7 @@ TYPED_TEST(TestCollection, MultipleRuleFailAndMatch) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 0); } @@ -205,7 +212,7 @@ TYPED_TEST(TestCollection, MultipleRuleFailAndMatch) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 1); } @@ -215,11 +222,11 @@ TYPED_TEST(TestCollection, MultipleRuleFailAndMatch) TYPED_TEST(TestCollection, SingleRuleMultipleCalls) { std::unordered_set seen_actions; - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); conditions.emplace_back( std::make_shared(std::move(targets), std::vector{}, @@ -228,18 +235,18 @@ TYPED_TEST(TestCollection, SingleRuleMultipleCalls) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); conditions.emplace_back( std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"}))); } - auto manifest = mb.build_manifest(); + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); TypeParam rule_collection; rule_collection.insert(rule); @@ -256,7 +263,7 @@ TYPED_TEST(TestCollection, SingleRuleMultipleCalls) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 0); } @@ -272,7 +279,7 @@ TYPED_TEST(TestCollection, SingleRuleMultipleCalls) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 1); } @@ -283,10 +290,10 @@ TYPED_TEST(TestCollection, SingleRuleMultipleCalls) TEST(TestPriorityCollection, MatchBothActions) { priority_collection rule_collection; - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -294,29 +301,32 @@ TEST(TestPriorityCollection, MatchBothActions) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type", "category1", + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); rule_collection.insert(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type", "category2", + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"redirect"}); rule_collection.insert(rule); } - auto manifest = mb.build_manifest(); - ddwaf::timer deadline{2s}; ddwaf::object_store store(manifest); std::unordered_set seen_actions; @@ -336,7 +346,7 @@ TEST(TestPriorityCollection, MatchBothActions) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 2); EXPECT_EQ(seen_actions.size(), 2); @@ -349,10 +359,10 @@ TEST(TestPriorityCollection, MatchBothActions) TEST(TestPriorityCollection, MatchOneAction) { priority_collection rule_collection; - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -360,29 +370,33 @@ TEST(TestPriorityCollection, MatchOneAction) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type", "category1", + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); rule_collection.insert(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type", "category2", + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"block"}); rule_collection.insert(rule); } - auto manifest = mb.build_manifest(); - ddwaf::timer deadline{2s}; ddwaf::object_store store(manifest); std::unordered_set seen_actions; @@ -401,7 +415,7 @@ TEST(TestPriorityCollection, MatchOneAction) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 1); EXPECT_EQ(seen_actions.size(), 1); @@ -413,10 +427,10 @@ TEST(TestPriorityCollection, MatchOneAction) TEST(TestPriorityCollection, MatchAllIfMissing) { priority_collection rule_collection; - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -424,29 +438,33 @@ TEST(TestPriorityCollection, MatchAllIfMissing) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type", "category1", + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); rule_collection.insert(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type", "category2", + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"block"}); rule_collection.insert(rule); } - auto manifest = mb.build_manifest(); - ddwaf::timer deadline{2s}; ddwaf::object_store store(manifest); std::unordered_set seen_actions; @@ -466,7 +484,7 @@ TEST(TestPriorityCollection, MatchAllIfMissing) std::vector events; ddwaf::timer deadline{2s}; - rule_collection.match(events, seen_actions, store, manifest, cache, {}, {}, deadline); + rule_collection.match(events, seen_actions, store, cache, {}, {}, {}, deadline); EXPECT_EQ(events.size(), 2); EXPECT_EQ(seen_actions.size(), 1); diff --git a/tests/condition_test.cpp b/tests/condition_test.cpp index 69ed3af32..a5a0ebcd5 100644 --- a/tests/condition_test.cpp +++ b/tests/condition_test.cpp @@ -10,17 +10,16 @@ using namespace ddwaf; TEST(TestCondition, Match) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("server.request.query", {})); - - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("server.request.query"), "server.request.query", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(".*", 0, true)); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "server.request.query", ddwaf_object_string(&tmp, "value")); @@ -29,7 +28,7 @@ TEST(TestCondition, Match) ddwaf::timer deadline{2s}; - auto match = cond->match(store, manifest, {}, true, deadline); + auto match = cond->match(store, {}, true, {}, deadline); EXPECT_TRUE(match.has_value()); EXPECT_STREQ(match->resolved.c_str(), "value"); @@ -42,17 +41,16 @@ TEST(TestCondition, Match) TEST(TestCondition, NoMatch) { - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{})); - ddwaf_object root, tmp; + 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")); @@ -61,23 +59,22 @@ TEST(TestCondition, NoMatch) ddwaf::timer deadline{2s}; - auto match = cond->match(store, manifest, {}, true, deadline); + auto match = cond->match(store, {}, true, {}, deadline); EXPECT_FALSE(match.has_value()); } TEST(TestCondition, ExcludeInput) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("server.request.query", {})); - - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("server.request.query"), "server.request.query", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(".*", 0, true)); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "server.request.query", ddwaf_object_string(&tmp, "value")); @@ -86,23 +83,23 @@ TEST(TestCondition, ExcludeInput) ddwaf::timer deadline{2s}; - auto match = cond->match(store, manifest, {&root.array[0]}, true, deadline); + auto match = cond->match(store, {&root.array[0]}, true, {}, deadline); EXPECT_FALSE(match.has_value()); } TEST(TestCondition, ExcludeKeyPath) { - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("server.request.query", {})); + std::vector targets; - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("server.request.query"), "server.request.query", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(".*", 0, true)); - ddwaf_object root, map, tmp; + ddwaf_object root; + ddwaf_object map; + ddwaf_object tmp; ddwaf_object_map(&map); ddwaf_object_map_add(&map, "key", ddwaf_object_string(&tmp, "value")); @@ -114,6 +111,6 @@ TEST(TestCondition, ExcludeKeyPath) ddwaf::timer deadline{2s}; - auto match = cond->match(store, manifest, {&map.array[0]}, true, deadline); + auto match = cond->match(store, {&map.array[0]}, true, {}, deadline); EXPECT_FALSE(match.has_value()); } diff --git a/tests/context_test.cpp b/tests/context_test.cpp index bb5fded55..c97dc48cd 100644 --- a/tests/context_test.cpp +++ b/tests/context_test.cpp @@ -12,7 +12,7 @@ using namespace ddwaf::exclusion; namespace ddwaf::test { class context : public ddwaf::context { public: - context(ddwaf::ruleset &ruleset, const ddwaf::config &config) : ddwaf::context(ruleset, config) + explicit context(std::shared_ptr ruleset) : ddwaf::context(std::move(ruleset)) {} bool insert(const ddwaf_object &object) { return store_.insert(object); } @@ -22,27 +22,30 @@ class context : public ddwaf::context { TEST(TestContext, MatchTimeout) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ddwaf::ruleset ruleset; - ruleset.insert_rule(rule); - ruleset.manifest = mb.build_manifest(); + auto ruleset = std::make_shared(); + ruleset->insert_rule(rule); + ruleset->manifest = manifest; ddwaf::timer deadline{0s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -52,27 +55,30 @@ TEST(TestContext, MatchTimeout) TEST(TestContext, NoMatch) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ddwaf::ruleset ruleset; - ruleset.insert_rule(rule); - ruleset.manifest = mb.build_manifest(); + auto ruleset = std::make_shared(); + ruleset->insert_rule(rule); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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")); ctx.insert(root); @@ -83,27 +89,30 @@ TEST(TestContext, NoMatch) TEST(TestContext, Match) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ddwaf::ruleset ruleset; - ruleset.insert_rule(rule); - ruleset.manifest = mb.build_manifest(); + auto ruleset = std::make_shared(); + ruleset->insert_rule(rule); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -114,11 +123,11 @@ TEST(TestContext, Match) TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -126,33 +135,40 @@ TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + auto rule = std::make_shared( - "id1", "name1", "type", "category1", std::move(conditions), std::vector{}); + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + auto rule = std::make_shared( - "id2", "name2", "type", "category2", std::move(conditions), std::vector{}); + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -181,11 +197,11 @@ TEST(TestContext, MatchMultipleRulesInCollectionSingleRun) TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -193,34 +209,41 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + auto rule = std::make_shared( - "id1", "name1", "type", "category1", std::move(conditions), std::vector{}); + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + // This rule has actions, so it'll be have priority - auto rule = std::make_shared("id2", "name2", "type", "category2", + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"block"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; { - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); @@ -237,9 +260,10 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) } { - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -258,11 +282,11 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -270,34 +294,41 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + auto rule = std::make_shared( - "id1", "name1", "type", "category1", std::move(conditions), std::vector{}); + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + auto rule = std::make_shared( - "id2", "name2", "type", "category2", std::move(conditions), std::vector{}); + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - ddwaf_object root, tmp; + 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); @@ -324,7 +355,8 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) } { - ddwaf_object root, tmp; + 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); @@ -336,11 +368,11 @@ TEST(TestContext, MatchMultipleRulesInCollectionDoubleRun) TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -348,34 +380,41 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + auto rule = std::make_shared( - "id1", "name1", "type", "category1", std::move(conditions), std::vector{}); + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type", "category2", + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"block"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - ddwaf_object root, tmp; + 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); @@ -404,7 +443,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) { // An existing match in a collection will not inhibit a match in a // priority collection. - ddwaf_object root, tmp; + 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); @@ -434,11 +474,11 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -446,34 +486,41 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type", "category1", + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + auto rule = std::make_shared( - "id2", "name2", "type", "category2", std::move(conditions), std::vector{}); + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - ddwaf_object root, tmp; + 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); @@ -502,7 +549,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) { // An existing match in a collection will not inhibit a match in a // priority collection. - ddwaf_object root, tmp; + 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); @@ -514,11 +562,11 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) TEST(TestContext, MatchMultipleRulesWithPriorityUntilAllActionsMet) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -526,34 +574,41 @@ TEST(TestContext, MatchMultipleRulesWithPriorityUntilAllActionsMet) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type", "category1", + std::unordered_map tags{ + {"type", "type"}, {"category", "category1"}}; + + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type", "category2", + std::unordered_map tags{ + {"type", "type"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"redirect"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - ddwaf_object root, tmp; + 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); @@ -582,7 +637,8 @@ TEST(TestContext, MatchMultipleRulesWithPriorityUntilAllActionsMet) { // An existing match in a collection will not inhibit a match in a // priority collection. - ddwaf_object root, tmp; + 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); @@ -612,11 +668,11 @@ TEST(TestContext, MatchMultipleRulesWithPriorityUntilAllActionsMet) TEST(TestContext, MatchMultipleCollectionsSingleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -624,33 +680,40 @@ TEST(TestContext, MatchMultipleCollectionsSingleRun) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type1", "category1", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "type1"}, {"category", "category1"}}; + + auto rule = std::make_shared( + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type2", "category2", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "type2"}, {"category", "category2"}}; - ruleset.insert_rule(rule); + auto rule = std::make_shared( + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); + + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -662,11 +725,11 @@ TEST(TestContext, MatchMultipleCollectionsSingleRun) TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -674,33 +737,40 @@ TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type1", "category1", + std::unordered_map tags{ + {"type", "type1"}, {"category", "category1"}}; + + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type2", "category2", + std::unordered_map tags{ + {"type", "type2"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"redirect"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -712,11 +782,11 @@ TEST(TestContext, MatchMultiplePriorityCollectionsSingleRun) TEST(TestContext, MatchMultipleCollectionsDoubleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -724,34 +794,41 @@ TEST(TestContext, MatchMultipleCollectionsDoubleRun) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type1", "category1", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "type1"}, {"category", "category1"}}; + + auto rule = std::make_shared( + "id1", "name1", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type2", "category2", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "type2"}, {"category", "category2"}}; - ruleset.insert_rule(rule); + auto rule = std::make_shared( + "id2", "name2", std::move(tags), std::move(conditions), std::vector{}); + + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - ddwaf_object root, tmp; + 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); @@ -761,7 +838,8 @@ TEST(TestContext, MatchMultipleCollectionsDoubleRun) } { - ddwaf_object root, tmp; + 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); @@ -773,11 +851,11 @@ TEST(TestContext, MatchMultipleCollectionsDoubleRun) TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -785,34 +863,41 @@ TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id1", "name1", "type1", "category1", + std::unordered_map tags{ + {"type", "type1"}, {"category", "category1"}}; + + auto rule = std::make_shared("id1", "name1", std::move(tags), std::move(conditions), std::vector{"block"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; - auto rule = std::make_shared("id2", "name2", "type2", "category2", + std::unordered_map tags{ + {"type", "type2"}, {"category", "category2"}}; + + auto rule = std::make_shared("id2", "name2", std::move(tags), std::move(conditions), std::vector{"redirect"}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - ddwaf_object root, tmp; + 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); @@ -822,7 +907,8 @@ TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) } { - ddwaf_object root, tmp; + 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); @@ -834,30 +920,33 @@ TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) TEST(TestContext, RuleFilterWithCondition) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; // Generate rule ddwaf::rule::ptr rule; { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } // Generate filter { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -867,15 +956,16 @@ TEST(TestContext, RuleFilterWithCondition) auto filter = std::make_shared( "1", std::move(conditions), std::set{rule}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); @@ -891,30 +981,33 @@ TEST(TestContext, RuleFilterWithCondition) TEST(TestContext, RuleFilterTimeout) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; // Generate rule ddwaf::rule::ptr rule; { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } // Generate filter { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -924,15 +1017,16 @@ TEST(TestContext, RuleFilterTimeout) auto filter = std::make_shared( "1", std::move(conditions), std::set{rule}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{0s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.1")); @@ -943,30 +1037,33 @@ TEST(TestContext, RuleFilterTimeout) TEST(TestContext, NoRuleFilterWithCondition) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; // Generate rule ddwaf::rule::ptr rule; { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } // Generate filter { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -976,15 +1073,16 @@ TEST(TestContext, NoRuleFilterWithCondition) auto filter = std::make_shared( "1", std::move(conditions), std::set{rule}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.0.2")); @@ -999,21 +1097,25 @@ TEST(TestContext, NoRuleFilterWithCondition) TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { - ddwaf::ruleset ruleset; + auto ruleset = std::make_shared(); // Generate rule constexpr unsigned num_rules = 9; std::vector rules; rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { - rules.emplace_back(std::make_shared("id" + std::to_string(i), "name", "type", - "category", std::vector{}, std::vector{})); - ruleset.insert_rule(rules.back()); + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared("id" + std::to_string(i), "name", + std::move(tags), std::vector{}, std::vector{})); + + ruleset->insert_rule(rules.back()); } ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { auto rules_to_exclude = ctx.filter_rules(deadline); @@ -1023,7 +1125,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { auto filter = std::make_shared("1", std::vector{}, std::set{rules[0], rules[1], rules[2]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 3); @@ -1035,7 +1137,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { auto filter = std::make_shared("2", std::vector{}, std::set{rules[3], rules[4], rules[5]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 6); @@ -1050,7 +1152,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) { auto filter = std::make_shared("3", std::vector{}, std::set{rules[6], rules[7], rules[8]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 9); @@ -1068,7 +1170,7 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRules) TEST(TestContext, MultipleRuleFiltersOverlappingRules) { - ddwaf::ruleset ruleset; + auto ruleset = std::make_shared(); // Generate rule constexpr unsigned num_rules = 9; @@ -1076,14 +1178,18 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::string id = "id" + std::to_string(i); - rules.emplace_back(std::make_shared(std::string(id), "name", "type", - "category", std::vector{}, std::vector{})); - ruleset.insert_rule(rules.back()); + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), + std::vector{}, std::vector{})); + + ruleset->insert_rule(rules.back()); } ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { auto rules_to_exclude = ctx.filter_rules(deadline); @@ -1093,7 +1199,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("1", std::vector{}, std::set{rules[0], rules[1], rules[2], rules[3]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 4); @@ -1106,7 +1212,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("2", std::vector{}, std::set{rules[2], rules[3], rules[4]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 5); @@ -1120,7 +1226,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("3", std::vector{}, std::set{rules[0], rules[5], rules[6]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 7); @@ -1136,7 +1242,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) { auto filter = std::make_shared("4", std::vector{}, std::set{rules[7], rules[8], rules[6]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 9); @@ -1155,7 +1261,7 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) auto filter = std::make_shared("5", std::vector{}, std::set{rules[0], rules[1], rules[2], rules[3], rules[4], rules[5], rules[6], rules[7], rules[8]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); auto rules_to_exclude = ctx.filter_rules(deadline); EXPECT_EQ(rules_to_exclude.size(), 9); @@ -1173,8 +1279,8 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRules) TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; // Generate rule constexpr unsigned num_rules = 10; @@ -1182,18 +1288,22 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::string id = "id" + std::to_string(i); - rules.emplace_back(std::make_shared(std::string(id), "name", "type", - "category", std::vector{}, std::vector{})); - ruleset.insert_rule(rules.back()); + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), + std::vector{}, std::vector{})); + + ruleset->insert_rule(rules.back()); } ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -1203,12 +1313,12 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) auto filter = std::make_shared("1", std::move(conditions), std::set{rules[0], rules[1], rules[2], rules[3], rules[4]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); @@ -1217,13 +1327,14 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) auto filter = std::make_shared("2", std::move(conditions), std::set{rules[5], rules[6], rules[7], rules[8], rules[9]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; { - ddwaf_object root, tmp; + 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); @@ -1238,7 +1349,8 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) } { - ddwaf_object root, tmp; + 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); @@ -1260,8 +1372,8 @@ TEST(TestContext, MultipleRuleFiltersNonOverlappingRulesWithConditions) TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) { - ddwaf::ruleset ruleset; - ddwaf::manifest_builder mb; + auto ruleset = std::make_shared(); + ddwaf::manifest manifest; // Generate rule constexpr unsigned num_rules = 10; @@ -1269,18 +1381,22 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) rules.reserve(num_rules); for (unsigned i = 0; i < num_rules; i++) { std::string id = "id" + std::to_string(i); - rules.emplace_back(std::make_shared(std::string(id), "name", "type", - "category", std::vector{}, std::vector{})); - ruleset.insert_rule(rules.back()); + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + + rules.emplace_back(std::make_shared(std::string(id), "name", std::move(tags), + std::vector{}, std::vector{})); + + ruleset->insert_rule(rules.back()); } ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( @@ -1291,12 +1407,12 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) auto filter = std::make_shared("1", std::move(conditions), std::set{ rules[0], rules[1], rules[2], rules[3], rules[4], rules[5], rules[6]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); @@ -1306,13 +1422,14 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) auto filter = std::make_shared("2", std::move(conditions), std::set{ rules[3], rules[4], rules[5], rules[6], rules[7], rules[8], rules[9]}); - ruleset.rule_filters.emplace(filter->get_id(), filter); + ruleset->rule_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; { - ddwaf_object root, tmp; + 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); @@ -1329,7 +1446,8 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) } { - ddwaf_object root, tmp; + 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); @@ -1351,35 +1469,38 @@ TEST(TestContext, MultipleRuleFiltersOverlappingRulesWithConditions) TEST(TestContext, InputFilterExclude) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); + ddwaf::manifest manifest; + condition::target_type client_ip{manifest.insert("http.client_ip"), "http.client_ip", {}}; - std::vector targets{client_ip}; + std::vector targets{client_ip}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip.root); std::vector filter_conditions; std::set filter_rules{rule}; auto filter = std::make_shared( "1", std::move(filter_conditions), std::move(filter_rules), std::move(obj_filter)); - ddwaf::ruleset ruleset; - ruleset.insert_rule(rule); - ruleset.manifest = mb.build_manifest(); - ruleset.input_filters.emplace(filter->get_id(), filter); + auto ruleset = std::make_shared(); + ruleset->insert_rule(rule); + ruleset->manifest = manifest; + ruleset->input_filters.emplace(filter->get_id(), filter); ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -1392,35 +1513,38 @@ TEST(TestContext, InputFilterExclude) TEST(TestContext, InputFilterExcludeRule) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); + ddwaf::manifest manifest; + condition::target_type client_ip{manifest.insert("http.client_ip"), "http.client_ip", {}}; - std::vector targets{client_ip}; + std::vector targets{client_ip}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip.root); std::vector filter_conditions; std::set filter_rules{rule}; auto filter = std::make_shared( "1", std::move(filter_conditions), std::move(filter_rules), std::move(obj_filter)); - ddwaf::ruleset ruleset; - ruleset.insert_rule(rule); - ruleset.manifest = mb.build_manifest(); - ruleset.input_filters.emplace(filter->get_id(), filter); + auto ruleset = std::make_shared(); + ruleset->insert_rule(rule); + ruleset->manifest = manifest; + ruleset->input_filters.emplace(filter->get_id(), filter); ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -1436,50 +1560,54 @@ TEST(TestContext, InputFilterExcludeRule) TEST(TestContext, InputFilterWithCondition) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + condition::target_type client_ip{manifest.insert("http.client_ip"), "http.client_ip", {}}; + condition::target_type usr_id{manifest.insert("usr.id"), "usr.id", {}}; - ddwaf::ruleset ruleset; + auto ruleset = std::make_shared(); { std::vector> conditions; - std::vector targets{client_ip}; + std::vector targets{client_ip}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); conditions.emplace_back(std::move(cond)); + std::unordered_map tags{ + {"type", "type"}, {"category", "category"}}; + auto rule = std::make_shared( - "id", "name", "type", "category", std::move(conditions), std::vector{}); + "id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip.root); std::vector> conditions; - std::vector targets{usr_id}; + std::vector targets{usr_id}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.emplace_back(std::move(cond)); - std::set filter_rules{ruleset.rules["id"]}; + std::set filter_rules{ruleset->rules["id"]}; auto filter = std::make_shared( "1", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; // Without usr.id, nothing should be excluded { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -1493,9 +1621,10 @@ TEST(TestContext, InputFilterWithCondition) // With usr.id != admin, nothing should be excluded { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admino")); @@ -1510,9 +1639,10 @@ TEST(TestContext, InputFilterWithCondition) // With usr.id == admin, there should be no matches { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -1527,59 +1657,66 @@ TEST(TestContext, InputFilterWithCondition) TEST(TestContext, InputFilterMultipleRules) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + condition::target_type client_ip{manifest.insert("http.client_ip"), "http.client_ip", {}}; + condition::target_type usr_id{manifest.insert("usr.id"), "usr.id", {}}; - ddwaf::ruleset ruleset; + auto ruleset = std::make_shared(); { std::vector> conditions; - std::vector targets{client_ip}; + std::vector targets{client_ip}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("ip_id", "name", "ip_type", "category", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "ip_type"}, {"category", "category"}}; + + auto rule = std::make_shared( + "ip_id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { std::vector> conditions; - std::vector targets{usr_id}; + std::vector targets{usr_id}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("usr_id", "name", "usr_type", "category", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "usr_type"}, {"category", "category"}}; + + auto rule = std::make_shared( + "usr_id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - object_filter obj_filter; - obj_filter.insert(client_ip); - obj_filter.insert(usr_id); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip.root); + obj_filter->insert(usr_id.root); std::vector> conditions; - std::set filter_rules{ruleset.rules["usr_id"], ruleset.rules["ip_id"]}; + std::set filter_rules{ruleset->rules["usr_id"], ruleset->rules["ip_id"]}; auto filter = std::make_shared( "1", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; // Without usr.id, nothing should be excluded { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -1595,9 +1732,10 @@ TEST(TestContext, InputFilterMultipleRules) // With usr.id != admin, nothing should be excluded { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admino")); @@ -1614,9 +1752,10 @@ TEST(TestContext, InputFilterMultipleRules) // With usr.id == admin, there should be no matches { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -1633,68 +1772,74 @@ TEST(TestContext, InputFilterMultipleRules) TEST(TestContext, InputFilterMultipleRulesMultipleFilters) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + condition::target_type client_ip{manifest.insert("http.client_ip"), "http.client_ip", {}}; + condition::target_type usr_id{manifest.insert("usr.id"), "usr.id", {}}; - ddwaf::ruleset ruleset; + auto ruleset = std::make_shared(); { std::vector> conditions; - std::vector targets{client_ip}; + std::vector targets{client_ip}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("ip_id", "name", "ip_type", "category", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "ip_type"}, {"category", "category"}}; + + auto rule = std::make_shared( + "ip_id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { std::vector> conditions; - std::vector targets{usr_id}; + std::vector targets{usr_id}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("usr_id", "name", "usr_type", "category", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "usr_type"}, {"category", "category"}}; + + auto rule = std::make_shared( + "usr_id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip.root); std::vector> conditions; - std::set filter_rules{ruleset.rules["ip_id"]}; + std::set filter_rules{ruleset->rules["ip_id"]}; auto filter = std::make_shared( "1", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } { - object_filter obj_filter; - obj_filter.insert(usr_id); + auto obj_filter = std::make_shared(); + obj_filter->insert(usr_id.root); std::vector> conditions; - std::set filter_rules{ruleset.rules["usr_id"]}; + std::set filter_rules{ruleset->rules["usr_id"]}; auto filter = std::make_shared( "2", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; // Without usr.id, nothing should be excluded { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); ddwaf_object root; ddwaf_object tmp; @@ -1713,7 +1858,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) // With usr.id != admin, nothing should be excluded { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); ddwaf_object root; ddwaf_object tmp; @@ -1733,7 +1878,7 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) // With usr.id == admin, there should be no matches { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); ddwaf_object root; ddwaf_object tmp; @@ -1753,101 +1898,112 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFilters) TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); - auto cookie_header = mb.insert("server.request.headers", {"cookie"}); + ddwaf::manifest manifest; + condition::target_type client_ip{manifest.insert("http.client_ip"), "http.client_ip", {}}; + condition::target_type usr_id{manifest.insert("usr.id"), "usr.id", {}}; + condition::target_type cookie_header{ + manifest.insert("server.request.headers"), "server.request.headers", {"cookie"}}; - ddwaf::ruleset ruleset; + auto ruleset = std::make_shared(); { std::vector> conditions; - std::vector targets{client_ip}; + std::vector targets{client_ip}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("ip_id", "name", "ip_type", "category", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "ip_type"}, {"category", "category"}}; + + auto rule = std::make_shared( + "ip_id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } { std::vector> conditions; - std::vector targets{usr_id}; + std::vector targets{usr_id}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("usr_id", "name", "usr_type", "category", - std::move(conditions), std::vector{}); + std::unordered_map tags{ + {"type", "usr_type"}, {"category", "category"}}; - ruleset.insert_rule(rule); + auto rule = std::make_shared( + "usr_id", "name", std::move(tags), std::move(conditions), std::vector{}); + + ruleset->insert_rule(rule); } { std::vector> conditions; - std::vector targets{cookie_header}; + std::vector targets{cookie_header}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"mycookie"})); conditions.emplace_back(std::move(cond)); - auto rule = std::make_shared("cookie_id", "name", "cookie_type", "category", + std::unordered_map tags{ + {"type", "cookie_type"}, {"category", "category"}}; + + auto rule = std::make_shared("cookie_id", "name", std::move(tags), std::move(conditions), std::vector{}); - ruleset.insert_rule(rule); + ruleset->insert_rule(rule); } - auto ip_rule = ruleset.rules["ip_id"]; - auto usr_rule = ruleset.rules["usr_id"]; - auto cookie_rule = ruleset.rules["cookie_id"]; + auto ip_rule = ruleset->rules["ip_id"]; + auto usr_rule = ruleset->rules["usr_id"]; + auto cookie_rule = ruleset->rules["cookie_id"]; { - object_filter obj_filter; - obj_filter.insert(client_ip); - obj_filter.insert(cookie_header); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip.root); + obj_filter->insert(cookie_header.root); std::vector> conditions; std::set filter_rules{ip_rule, cookie_rule}; auto filter = std::make_shared( "1", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } { - object_filter obj_filter; - obj_filter.insert(usr_id); - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(usr_id.root); + obj_filter->insert(client_ip.root); std::vector> conditions; std::set filter_rules{usr_rule, ip_rule}; auto filter = std::make_shared( "2", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } { - object_filter obj_filter; - obj_filter.insert(usr_id); - obj_filter.insert(cookie_header); + auto obj_filter = std::make_shared(); + obj_filter->insert(usr_id.root); + obj_filter->insert(cookie_header.root); std::vector> conditions; std::set filter_rules{usr_rule, cookie_rule}; auto filter = std::make_shared( "3", std::move(conditions), std::move(filter_rules), std::move(obj_filter)); - ruleset.input_filters.emplace(filter->get_id(), filter); + ruleset->input_filters.emplace(filter->get_id(), filter); } - ruleset.manifest = mb.build_manifest(); + ruleset->manifest = manifest; { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -1865,9 +2021,10 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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); @@ -1885,9 +2042,11 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, headers, tmp; + ddwaf_object root; + ddwaf_object headers; + ddwaf_object tmp; ddwaf_object_map(&headers); ddwaf_object_map_add(&headers, "cookie", ddwaf_object_string(&tmp, "mycookie")); @@ -1909,9 +2068,10 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -1930,9 +2090,11 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, headers, tmp; + ddwaf_object root; + ddwaf_object headers; + ddwaf_object tmp; ddwaf_object_map(&headers); ddwaf_object_map_add(&headers, "cookie", ddwaf_object_string(&tmp, "mycookie")); @@ -1955,9 +2117,11 @@ TEST(TestContext, InputFilterMultipleRulesMultipleFiltersMultipleObjects) { ddwaf::timer deadline{2s}; - ddwaf::test::context ctx(ruleset, ddwaf::config()); + ddwaf::test::context ctx(ruleset); - ddwaf_object root, headers, tmp; + ddwaf_object root; + ddwaf_object headers; + ddwaf_object tmp; ddwaf_object_map(&headers); ddwaf_object_map_add(&headers, "cookie", ddwaf_object_string(&tmp, "mycookie")); diff --git a/tests/input_filter_test.cpp b/tests/input_filter_test.cpp index 9fdcf2d39..0be8c5924 100644 --- a/tests/input_filter_test.cpp +++ b/tests/input_filter_test.cpp @@ -11,25 +11,26 @@ using namespace ddwaf::exclusion; TEST(TestInputFilter, InputExclusionNoConditions) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + object_store store(manifest); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "query", ddwaf_object_string(&tmp, "value")); store.insert(root); - object_filter obj_filter; - obj_filter.insert(query, {}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {}); input_filter filter( - "filter", {}, {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + "filter", {}, {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -38,12 +39,14 @@ TEST(TestInputFilter, InputExclusionNoConditions) TEST(TestInputFilter, ObjectExclusionNoConditions) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + object_store store(manifest); - ddwaf_object root, child, tmp; + ddwaf_object root; + ddwaf_object child; + ddwaf_object tmp; ddwaf_object_map(&child); ddwaf_object_map_add(&child, "params", ddwaf_object_string(&tmp, "param")); @@ -52,15 +55,15 @@ TEST(TestInputFilter, ObjectExclusionNoConditions) store.insert(root); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter( - "filter", {}, {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + "filter", {}, {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -69,32 +72,32 @@ TEST(TestInputFilter, ObjectExclusionNoConditions) TEST(TestInputFilter, InputExclusionWithCondition) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; - ddwaf_object root, tmp; + 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(manifest); store.insert(root); - object_filter obj_filter; - obj_filter.insert(client_ip, {}); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip, {}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -103,49 +106,50 @@ TEST(TestInputFilter, InputExclusionWithCondition) TEST(TestInputFilter, InputExclusionFailedCondition) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; - ddwaf_object root, tmp; + 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")); ddwaf::object_store store(manifest); store.insert(root); - object_filter obj_filter; - obj_filter.insert(client_ip, {}); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip, {}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_FALSE(opt_spec.has_value()); } TEST(TestInputFilter, ObjectExclusionWithCondition) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto query = manifest.insert("query"); - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; - ddwaf_object root, child, tmp; + ddwaf_object root; + ddwaf_object child; + ddwaf_object tmp; ddwaf_object_map(&child); ddwaf_object_map_add(&child, "params", ddwaf_object_string(&tmp, "value")); @@ -156,16 +160,16 @@ TEST(TestInputFilter, ObjectExclusionWithCondition) ddwaf::object_store store(manifest); store.insert(root); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -174,18 +178,19 @@ TEST(TestInputFilter, ObjectExclusionWithCondition) TEST(TestInputFilter, ObjectExclusionFailedCondition) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto query = manifest.insert("query"); - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; - ddwaf_object root, child, tmp; + ddwaf_object root; + ddwaf_object child; + ddwaf_object tmp; ddwaf_object_map(&child); ddwaf_object_map_add(&child, "params", ddwaf_object_string(&tmp, "value")); @@ -196,28 +201,28 @@ TEST(TestInputFilter, ObjectExclusionFailedCondition) ddwaf::object_store store(manifest); store.insert(root); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_FALSE(opt_spec.has_value()); } TEST(TestInputFilter, InputValidateCachedMatch) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -225,24 +230,24 @@ TEST(TestInputFilter, InputValidateCachedMatch) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(usr_id); + auto obj_filter = std::make_shared(); + obj_filter->insert(usr_id); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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 // matched on the second run. input_filter::cache_type cache; { - ddwaf_object root, tmp; + 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")); @@ -250,11 +255,12 @@ TEST(TestInputFilter, InputValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -262,7 +268,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -272,13 +278,13 @@ TEST(TestInputFilter, InputValidateCachedMatch) TEST(TestInputFilter, InputMatchWithoutCache) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -286,22 +292,22 @@ TEST(TestInputFilter, InputMatchWithoutCache) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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). { - ddwaf_object root, tmp; + 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")); @@ -310,11 +316,12 @@ TEST(TestInputFilter, InputMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -323,19 +330,19 @@ TEST(TestInputFilter, InputMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } } TEST(TestInputFilter, InputNoMatchWithoutCache) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -343,17 +350,16 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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 @@ -361,7 +367,8 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) ddwaf::object_store store(manifest); { - ddwaf_object root, tmp; + 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")); @@ -369,11 +376,12 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -383,7 +391,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -393,13 +401,13 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) TEST(TestInputFilter, InputCachedMatchSecondRun) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -407,17 +415,16 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(client_ip); + auto obj_filter = std::make_shared(); + obj_filter->insert(client_ip); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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 @@ -426,7 +433,8 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) input_filter::cache_type cache; { - ddwaf_object root, tmp; + 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_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -434,7 +442,7 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -442,27 +450,28 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) } { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "random", ddwaf_object_string(&tmp, "random")); store.insert(root); ddwaf::timer deadline{2s}; - ASSERT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + ASSERT_FALSE(filter.match(store, cache, deadline).has_value()); } } TEST(TestInputFilter, ObjectValidateCachedMatch) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); - auto query = mb.insert("query", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); + auto query = manifest.insert("query"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -470,24 +479,25 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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 // matched on the second run. input_filter::cache_type cache; { - ddwaf_object root, object, tmp; + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; ddwaf_object_map(&object); ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); @@ -499,11 +509,13 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } { - ddwaf_object root, object, tmp; + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; ddwaf_object_map(&object); ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); @@ -515,7 +527,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -524,14 +536,14 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) TEST(TestInputFilter, ObjectMatchWithoutCache) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); - auto query = mb.insert("query", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); + auto query = manifest.insert("query"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -539,22 +551,23 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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). { - ddwaf_object root, object, tmp; + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; ddwaf_object_map(&object); ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); @@ -567,11 +580,13 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } { - ddwaf_object root, object, tmp; + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; ddwaf_object_map(&object); ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); @@ -584,20 +599,20 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } } TEST(TestInputFilter, ObjectNoMatchWithoutCache) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); - auto query = mb.insert("query", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); + auto query = manifest.insert("query"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -605,17 +620,16 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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 @@ -623,7 +637,9 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) ddwaf::object_store store(manifest); { - ddwaf_object root, object, tmp; + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; ddwaf_object_map(&object); ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); @@ -635,11 +651,12 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + EXPECT_FALSE(filter.match(store, cache, deadline).has_value()); } { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "admin")); @@ -647,7 +664,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) ddwaf::timer deadline{2s}; input_filter::cache_type cache; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); @@ -656,14 +673,14 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) TEST(TestInputFilter, ObjectCachedMatchSecondRun) { - ddwaf::manifest_builder mb; - auto client_ip = mb.insert("http.client_ip", {}); - auto usr_id = mb.insert("usr.id", {}); - auto query = mb.insert("query", {}); + ddwaf::manifest manifest; + auto client_ip = manifest.insert("http.client_ip"); + auto usr_id = manifest.insert("usr.id"); + auto query = manifest.insert("query"); std::vector> conditions; { - std::vector targets{client_ip}; + std::vector targets{{client_ip, "http.client_ip", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -671,17 +688,16 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) } { - std::vector targets{usr_id}; + std::vector targets{{usr_id, "usr.id", {}}}; auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - object_filter obj_filter; - obj_filter.insert(query, {"params"}); + auto obj_filter = std::make_shared(); + obj_filter->insert(query, {"params"}); input_filter filter("filter", std::move(conditions), - {std::make_shared(rule("", "", "", "", {}))}, std::move(obj_filter)); + {std::make_shared(rule("", "", {}, {}))}, std::move(obj_filter)); // 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 @@ -690,7 +706,9 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) input_filter::cache_type cache; { - ddwaf_object root, object, tmp; + ddwaf_object root; + ddwaf_object object; + ddwaf_object tmp; ddwaf_object_map(&object); ddwaf_object_map_add(&object, "params", ddwaf_object_string(&tmp, "value")); @@ -702,20 +720,21 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - auto opt_spec = filter.match(store, manifest, cache, deadline); + auto opt_spec = filter.match(store, cache, deadline); ASSERT_TRUE(opt_spec.has_value()); EXPECT_EQ(opt_spec->rules.size(), 1); EXPECT_EQ(opt_spec->objects.size(), 1); } { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "random", ddwaf_object_string(&tmp, "random")); store.insert(root); ddwaf::timer deadline{2s}; - ASSERT_FALSE(filter.match(store, manifest, cache, deadline).has_value()); + ASSERT_FALSE(filter.match(store, cache, deadline).has_value()); } } diff --git a/tests/interface_test.cpp b/tests/interface_test.cpp index 864a4eff3..e2de8789e 100644 --- a/tests/interface_test.cpp +++ b/tests/interface_test.cpp @@ -32,9 +32,9 @@ TEST(TestInterface, RootAddresses) ddwaf_destroy(handle); } -TEST(TestInterface, RuleDatIDs) +TEST(TestInterface, HandleLifetime) { - auto rule = readFile("rule_data.yaml"); + auto rule = readFile("interface.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -43,17 +43,85 @@ TEST(TestInterface, RuleDatIDs) ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); - uint32_t size; - const char *const *ids = ddwaf_required_rule_data_ids(handle, &size); - EXPECT_EQ(size, 2); + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + // Destroying the handle should not invalidate it + ddwaf_destroy(handle); + + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object tmp; + ddwaf_object param_key = DDWAF_OBJECT_ARRAY, param_val = DDWAF_OBJECT_ARRAY; + + ddwaf_object_array_add(¶m_key, ddwaf_object_unsigned(&tmp, 4242)); + ddwaf_object_array_add(¶m_key, ddwaf_object_string(&tmp, "randomString")); + + ddwaf_object_array_add(¶m_val, ddwaf_object_string(&tmp, "rule1")); + + ddwaf_object_map_add(¶meter, "value1", ¶m_key); + ddwaf_object_map_add(¶meter, "value2", ¶m_val); + + EXPECT_EQ(ddwaf_run(context, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + ddwaf_context_destroy(context); +} + +TEST(TestInterface, HandleLifetimeMultipleContexts) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; - std::set available_ids{"usr_data", "ip_data"}; - while ((size--) != 0U) { EXPECT_NE(available_ids.find(ids[size]), available_ids.end()); } + ddwaf_handle handle = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context1 = ddwaf_context_init(handle); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle); + ASSERT_NE(context2, nullptr); + // Destroying the handle should not invalidate it ddwaf_destroy(handle); + + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object tmp; + ddwaf_object param_key = DDWAF_OBJECT_ARRAY; + ddwaf_object param_val = DDWAF_OBJECT_ARRAY; + + ddwaf_object_array_add(¶m_key, ddwaf_object_unsigned(&tmp, 4242)); + ddwaf_object_array_add(¶m_key, ddwaf_object_string(&tmp, "randomString")); + + ddwaf_object_array_add(¶m_val, ddwaf_object_string(&tmp, "rule1")); + + ddwaf_object_map_add(¶meter, "value1", ¶m_key); + ddwaf_object_map_add(¶meter, "value2", ¶m_val); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + ddwaf_context_destroy(context1); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + ddwaf_context_destroy(context2); + + ddwaf_object_free(¶meter); +} + +TEST(TestInterface, InvalidVersion) +{ + auto rule = readRule("{version: 3.0, rules: []}"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_EQ(handle1, nullptr); + ddwaf_object_free(&rule); } -TEST(TestInterface, EmptyRuleDatIDs) +TEST(TestInterface, UpdateEmpty) { auto rule = readFile("interface.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -64,15 +132,15 @@ TEST(TestInterface, EmptyRuleDatIDs) ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); - uint32_t size; - const char *const *ids = ddwaf_required_rule_data_ids(handle, &size); - EXPECT_EQ(ids, nullptr); - EXPECT_EQ(size, 0); + rule = readRule("{}"); + ddwaf_handle new_handle = ddwaf_update(handle, &rule, nullptr); + ASSERT_EQ(new_handle, nullptr); + ddwaf_object_free(&rule); ddwaf_destroy(handle); } -TEST(TestInterface, HandleLifetime) +TEST(TestInterface, UpdateRules) { auto rule = readFile("interface.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -83,30 +151,47 @@ TEST(TestInterface, HandleLifetime) ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); + ddwaf_context context1 = ddwaf_context_init(handle); + ASSERT_NE(context1, nullptr); + + rule = readFile("interface3.yaml"); + ddwaf_handle new_handle = ddwaf_update(handle, &rule, nullptr); + ASSERT_NE(new_handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context2 = ddwaf_context_init(new_handle); + ASSERT_NE(context2, nullptr); // Destroying the handle should not invalidate it ddwaf_destroy(handle); + ddwaf_destroy(new_handle); - ddwaf_object parameter = DDWAF_OBJECT_MAP, tmp; - ddwaf_object param_key = DDWAF_OBJECT_ARRAY, param_val = DDWAF_OBJECT_ARRAY; + ddwaf_object tmp; + { + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); - ddwaf_object_array_add(¶m_key, ddwaf_object_unsigned(&tmp, 4242)); - ddwaf_object_array_add(¶m_key, ddwaf_object_string(&tmp, "randomString")); + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); - ddwaf_object_array_add(¶m_val, ddwaf_object_string(&tmp, "rule1")); + ddwaf_object_free(¶meter); + } - ddwaf_object_map_add(¶meter, "value1", ¶m_key); - ddwaf_object_map_add(¶meter, "value2", ¶m_val); + { + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); - EXPECT_EQ(ddwaf_run(context, ¶meter, NULL, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); - ddwaf_object_free(¶meter); - ddwaf_context_destroy(context); + ddwaf_object_free(¶meter); + } + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); } -TEST(TestInterface, HandleLifetimeMultipleContexts) +TEST(TestInterface, UpdateInvalidRules) { auto rule = readFile("interface.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -117,31 +202,1364 @@ TEST(TestInterface, HandleLifetimeMultipleContexts) ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); - ddwaf_context context1 = ddwaf_context_init(handle); + rule = readRule("{rules: []}"); + ddwaf_handle new_handle = ddwaf_update(handle, &rule, nullptr); + ASSERT_EQ(new_handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_destroy(handle); +} + +TEST(TestInterface, UpdateDisableEnableRuleByID) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); - ddwaf_context context2 = ddwaf_context_init(handle); + ddwaf_handle handle2; + { + auto overrides = + readRule(R"({rules_override: [{rules_target: [{rule_id: 1}], enabled: false}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + ddwaf_context context2 = ddwaf_context_init(handle2); ASSERT_NE(context2, nullptr); - // Destroying the handle should not invalidate it - ddwaf_destroy(handle); + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); - ddwaf_object parameter = DDWAF_OBJECT_MAP, tmp; - ddwaf_object param_key = DDWAF_OBJECT_ARRAY, param_val = DDWAF_OBJECT_ARRAY; + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); - ddwaf_object_array_add(¶m_key, ddwaf_object_unsigned(&tmp, 4242)); - ddwaf_object_array_add(¶m_key, ddwaf_object_string(&tmp, "randomString")); + ddwaf_object_free(¶meter); + } - ddwaf_object_array_add(¶m_val, ddwaf_object_string(&tmp, "rule1")); + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); - ddwaf_object_map_add(¶meter, "value1", ¶m_key); - ddwaf_object_map_add(¶meter, "value2", ¶m_val); + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + } - EXPECT_EQ(ddwaf_run(context1, ¶meter, NULL, LONG_TIME), DDWAF_MATCH); ddwaf_context_destroy(context1); + ddwaf_destroy(handle1); + + ddwaf_handle handle3; + { + auto overrides = readRule(R"({rules_override: []})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + } - EXPECT_EQ(ddwaf_run(context2, ¶meter, NULL, LONG_TIME), DDWAF_MATCH); ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} - ddwaf_object_free(¶meter); +TEST(TestInterface, UpdateDisableEnableRuleByTags) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_handle handle2; + { + auto overrides = readRule( + R"({rules_override: [{rules_target: [{tags: {type: flow2}}], enabled: false}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + } + + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + } + + ddwaf_context_destroy(context1); + ddwaf_context_destroy(context2); + ddwaf_destroy(handle1); + + ddwaf_handle handle3; + { + auto overrides = readRule(R"({rules_override: []})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + } + + { + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + } + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateActionsByID) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto overrides = + readRule(R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result1.actions.size, 0); + EXPECT_EQ(result2.actions.size, 1); + EXPECT_STREQ(result2.actions.array[0], "block"); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result1.actions.size, 0); + EXPECT_EQ(result2.actions.size, 0); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + ddwaf_destroy(handle1); + + ddwaf_handle handle3; + { + auto overrides = + readRule(R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [redirect]}]})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + ddwaf_result result2; + ddwaf_result result3; + + EXPECT_EQ(ddwaf_run(context2, ¶meter, &result2, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context3, ¶meter, &result3, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result2.actions.size, 1); + EXPECT_EQ(result3.actions.size, 1); + EXPECT_STREQ(result2.actions.array[0], "block"); + EXPECT_STREQ(result3.actions.array[0], "redirect"); + + ddwaf_result_free(&result2); + ddwaf_result_free(&result3); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + } + + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateActionsByTags) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto overrides = readRule( + R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], on_match: [block]}]})"); + handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result1.actions.size, 0); + EXPECT_EQ(result2.actions.size, 1); + EXPECT_STREQ(result2.actions.array[0], "block"); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + ddwaf_result result1; + ddwaf_result result2; + + EXPECT_EQ(ddwaf_run(context1, ¶meter, &result1, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, &result2, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result1.actions.size, 0); + EXPECT_EQ(result2.actions.size, 0); + + ddwaf_result_free(&result1); + ddwaf_result_free(&result2); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); +} + +TEST(TestInterface, UpdateInvalidOverrides) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + auto overrides = readRule(R"({rules_override: [{enabled: false}]})"); + ddwaf_handle handle2 = ddwaf_update(handle1, &overrides, nullptr); + ASSERT_EQ(handle2, nullptr); + ddwaf_object_free(&overrides); + + ddwaf_destroy(handle1); +} + +TEST(TestInterface, UpdateRuleData) +{ + auto rule = readFile("rule_data.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto data = readRule( + R"({rules_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 0}]}]})"); + handle2 = ddwaf_update(handle1, &data, nullptr); + ddwaf_object_free(&data); + } + + ddwaf_handle handle3; + { + auto data = readRule( + R"({rules_data: [{id: usr_data, type: data_with_expiration, data: [{value: paco, expiration: 0}]}]})"); + handle3 = ddwaf_update(handle2, &data, nullptr); + ddwaf_object_free(&data); + } + + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object tmp; + { + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + } + + { + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "usr.id", ddwaf_object_string(&tmp, "paco")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + } + + ddwaf_context_destroy(context1); + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateAndRevertRuleData) +{ + auto rule = readFile("rule_data.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto data = readRule( + R"({rules_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 0}]}]})"); + handle2 = ddwaf_update(handle1, &data, nullptr); + ddwaf_object_free(&data); + } + + ddwaf_object tmp; + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context1); + ddwaf_context_destroy(context2); + } + + ddwaf_handle handle3; + { + auto data = readRule(R"({rules_data: []})"); + handle3 = ddwaf_update(handle2, &data, nullptr); + ddwaf_object_free(&data); + } + + { + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + } + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateInvalidRuleData) +{ + auto rule = readFile("rule_data.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + // A rules_data with unrelated keys is considered an empty rules_data + auto data = readRule( + R"({rules_data: [{id: ipo_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 0}]}]})"); + ddwaf_handle handle2 = ddwaf_update(handle1, &data, nullptr); + EXPECT_NE(handle2, nullptr); + ddwaf_object_free(&data); + + ddwaf_destroy(handle1); + ddwaf_destroy(handle2); +} + +TEST(TestInterface, UpdateRuleExclusions) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto exclusions = readRule(R"({exclusions: [{id: 1, rules_target: [{rule_id: 1}]}]})"); + handle2 = ddwaf_update(handle1, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + ddwaf_destroy(handle1); + + ddwaf_handle handle3; + { + auto exclusions = readRule(R"({exclusions: []})"); + handle3 = ddwaf_update(handle2, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + { + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context3); + ddwaf_context_destroy(context2); + } + + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateInputExclusions) +{ + auto rule = readFile("interface.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + ddwaf_handle handle2; + { + auto exclusions = readRule(R"({exclusions: [{id: 1, inputs: [{address: value1}]}]})"); + handle2 = ddwaf_update(handle1, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule2")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value2", ddwaf_object_string(&tmp, "rule3")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + ddwaf_destroy(handle1); + + ddwaf_handle handle3; + { + auto exclusions = readRule(R"({exclusions: []})"); + handle3 = ddwaf_update(handle2, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + { + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "value1", ddwaf_object_string(&tmp, "rule1")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context3); + ddwaf_context_destroy(context2); + } + + ddwaf_destroy(handle2); + ddwaf_destroy(handle3); +} + +TEST(TestInterface, UpdateEverything) +{ + auto rule = readFile("interface_with_data.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + + ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ASSERT_NE(handle1, nullptr); + ddwaf_object_free(&rule); + + // After this update: + // - No rule will match server.request.query + ddwaf_handle handle2; + { + auto exclusions = + readRule(R"({exclusions: [{id: 1, inputs: [{address: server.request.query}]}]})"); + handle2 = ddwaf_update(handle1, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + { + ddwaf_context context1 = ddwaf_context_init(handle1); + ASSERT_NE(context1, nullptr); + + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.params", ddwaf_object_string(&tmp, "rule4")); + + EXPECT_EQ(ddwaf_run(context1, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context1); + } + + // After this update: + // - No rule will match server.request.query + // - Rules with confidence=1 will provide a block action + ddwaf_handle handle3; + { + auto overrides = readRule( + R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], on_match: [block]}]})"); + handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_object_free(&overrides); + } + + { + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.response.status", ddwaf_object_string(&tmp, "rule5")); + + ddwaf_result result2; + ddwaf_result result3; + + EXPECT_EQ(ddwaf_run(context2, ¶meter, &result2, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context3, ¶meter, &result3, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result2.actions.size, 0); + EXPECT_EQ(result3.actions.size, 1); + EXPECT_STREQ(result3.actions.array[0], "block"); + + ddwaf_result_free(&result2); + ddwaf_result_free(&result3); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + } + + { + ddwaf_context context2 = ddwaf_context_init(handle2); + ASSERT_NE(context2, nullptr); + + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + EXPECT_EQ(ddwaf_run(context2, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context3, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context2); + ddwaf_context_destroy(context3); + } + + // After this update: + // - No rule will match server.request.query + // - Rules with confidence=1 will provide a block action + // - Rules with ip_data or usr_data will now match + ddwaf_handle handle4; + { + auto data = readRule( + R"({rules_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 0}]},{id: usr_data, type: data_with_expiration, data: [{value: admin, expiration 0}]}]})"); + handle4 = ddwaf_update(handle3, &data, nullptr); + ddwaf_object_free(&data); + } + + ASSERT_NE(handle4, nullptr); + + { + ddwaf_context context3 = ddwaf_context_init(handle3); + ASSERT_NE(context3, nullptr); + + ddwaf_context context4 = ddwaf_context_init(handle4); + ASSERT_NE(context4, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + ddwaf_result result3; + ddwaf_result result4; + + EXPECT_EQ(ddwaf_run(context3, ¶meter, &result3, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context4, ¶meter, &result4, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result3.actions.size, 0); + EXPECT_EQ(result4.actions.size, 1); + EXPECT_STREQ(result4.actions.array[0], "block"); + + ddwaf_result_free(&result3); + ddwaf_result_free(&result4); + + ddwaf_context_destroy(context3); + ddwaf_context_destroy(context4); + } + + { + ddwaf_context context4 = ddwaf_context_init(handle4); + ASSERT_NE(context4, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + EXPECT_EQ(ddwaf_run(context4, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context4); + } + + // After this update: + // - No rule will match server.request.query + // - Rules with confidence=1 will provide a block action + // - Rules with ip_data or usr_data will now match + // - The following rules will be removed: rule3, rule4, rule5 + ddwaf_handle handle5; + { + auto data = readFile("rule_data.yaml"); + handle5 = ddwaf_update(handle4, &data, nullptr); + ddwaf_object_free(&data); + } + + ASSERT_NE(handle5, nullptr); + + { + ddwaf_context context4 = ddwaf_context_init(handle4); + ASSERT_NE(context4, nullptr); + + ddwaf_context context5 = ddwaf_context_init(handle5); + ASSERT_NE(context5, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + ddwaf_result result4; + ddwaf_result result5; + + EXPECT_EQ(ddwaf_run(context4, ¶meter, &result4, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context5, ¶meter, &result5, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result4.actions.size, 1); + EXPECT_STREQ(result4.actions.array[0], "block"); + EXPECT_EQ(result5.actions.size, 1); + EXPECT_STREQ(result5.actions.array[0], "block"); + + ddwaf_result_free(&result4); + ddwaf_result_free(&result5); + + ddwaf_context_destroy(context4); + ddwaf_context_destroy(context5); + } + + { + ddwaf_context context4 = ddwaf_context_init(handle4); + ASSERT_NE(context4, nullptr); + + ddwaf_context context5 = ddwaf_context_init(handle5); + ASSERT_NE(context5, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(¶meter, "usr.id", ddwaf_object_string(&tmp, "admin")); + + EXPECT_EQ(ddwaf_run(context4, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context5, ¶meter, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context4); + ddwaf_context_destroy(context5); + } + + { + ddwaf_context context4 = ddwaf_context_init(handle4); + ASSERT_NE(context4, nullptr); + + ddwaf_context context5 = ddwaf_context_init(handle5); + ASSERT_NE(context5, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.response.status", ddwaf_object_string(&tmp, "rule5")); + + ddwaf_result result4; + ddwaf_result result5; + + EXPECT_EQ(ddwaf_run(context4, ¶meter, &result4, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context5, ¶meter, &result5, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result4.actions.size, 1); + EXPECT_STREQ(result4.actions.array[0], "block"); + EXPECT_EQ(result5.actions.size, 0); + + ddwaf_result_free(&result4); + ddwaf_result_free(&result5); + + ddwaf_context_destroy(context4); + ddwaf_context_destroy(context5); + } + + // After this update: + // - No rule will match server.request.query + // - Rules with confidence=1 will provide a block action + // - Rules with ip_data or usr_data will now match + // - The following rules be back: rule3, rule4, rule5 + ddwaf_handle handle6; + { + auto data = readFile("interface_with_data.yaml"); + handle6 = ddwaf_update(handle5, &data, nullptr); + ddwaf_object_free(&data); + } + + ASSERT_NE(handle6, nullptr); + + { + ddwaf_context context5 = ddwaf_context_init(handle5); + ASSERT_NE(context5, nullptr); + + ddwaf_context context6 = ddwaf_context_init(handle6); + ASSERT_NE(context6, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.response.status", ddwaf_object_string(&tmp, "rule5")); + + ddwaf_result result5; + ddwaf_result result6; + + EXPECT_EQ(ddwaf_run(context5, ¶meter, &result5, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context6, ¶meter, &result6, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result5.actions.size, 0); + EXPECT_EQ(result6.actions.size, 1); + EXPECT_STREQ(result6.actions.array[0], "block"); + + ddwaf_result_free(&result5); + ddwaf_result_free(&result6); + + ddwaf_context_destroy(context5); + ddwaf_context_destroy(context6); + } + + { + ddwaf_context context5 = ddwaf_context_init(handle5); + ASSERT_NE(context5, nullptr); + + ddwaf_context context6 = ddwaf_context_init(handle6); + ASSERT_NE(context6, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + ddwaf_result result5; + ddwaf_result result6; + + EXPECT_EQ(ddwaf_run(context5, ¶meter, &result5, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context6, ¶meter, &result6, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result5.actions.size, 1); + EXPECT_STREQ(result5.actions.array[0], "block"); + EXPECT_EQ(result6.actions.size, 1); + EXPECT_STREQ(result6.actions.array[0], "block"); + + ddwaf_result_free(&result5); + ddwaf_result_free(&result6); + + ddwaf_context_destroy(context5); + ddwaf_context_destroy(context6); + } + + { + ddwaf_context context6 = ddwaf_context_init(handle6); + ASSERT_NE(context6, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + EXPECT_EQ(ddwaf_run(context6, ¶meter, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + ddwaf_context_destroy(context6); + } + + // After this update: + // - Rules with confidence=1 will provide a block action + // - Rules with ip_data or usr_data will now match + ddwaf_handle handle7; + { + auto exclusions = readRule(R"({exclusions: []})"); + handle7 = ddwaf_update(handle6, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + ASSERT_NE(handle7, nullptr); + + { + ddwaf_context context6 = ddwaf_context_init(handle6); + ASSERT_NE(context6, nullptr); + + ddwaf_context context7 = ddwaf_context_init(handle7); + ASSERT_NE(context7, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + ddwaf_result result6; + ddwaf_result result7; + + EXPECT_EQ(ddwaf_run(context6, ¶meter, &result6, LONG_TIME), DDWAF_OK); + EXPECT_EQ(ddwaf_run(context7, ¶meter, &result7, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result6.actions.size, 0); + EXPECT_EQ(result7.actions.size, 1); + EXPECT_STREQ(result7.actions.array[0], "block"); + + ddwaf_result_free(&result6); + ddwaf_result_free(&result7); + + ddwaf_context_destroy(context6); + ddwaf_context_destroy(context7); + } + + // After this update: + // - Rules with ip_data or usr_data will now match + ddwaf_handle handle8; + { + auto exclusions = readRule(R"({rules_override: []})"); + handle8 = ddwaf_update(handle7, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + ASSERT_NE(handle8, nullptr); + + { + ddwaf_context context7 = ddwaf_context_init(handle7); + ASSERT_NE(context7, nullptr); + + ddwaf_context context8 = ddwaf_context_init(handle8); + ASSERT_NE(context8, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + ddwaf_result result7; + ddwaf_result result8; + + EXPECT_EQ(ddwaf_run(context7, ¶meter, &result7, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context8, ¶meter, &result8, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result7.actions.size, 1); + EXPECT_STREQ(result7.actions.array[0], "block"); + EXPECT_EQ(result8.actions.size, 0); + + ddwaf_result_free(&result7); + ddwaf_result_free(&result8); + + ddwaf_context_destroy(context7); + ddwaf_context_destroy(context8); + } + + { + ddwaf_context context7 = ddwaf_context_init(handle7); + ASSERT_NE(context7, nullptr); + + ddwaf_context context8 = ddwaf_context_init(handle8); + ASSERT_NE(context8, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + ddwaf_result result7; + ddwaf_result result8; + + EXPECT_EQ(ddwaf_run(context7, ¶meter, &result7, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context8, ¶meter, &result8, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result7.actions.size, 1); + EXPECT_STREQ(result7.actions.array[0], "block"); + EXPECT_EQ(result8.actions.size, 0); + + ddwaf_result_free(&result7); + ddwaf_result_free(&result8); + + ddwaf_context_destroy(context7); + ddwaf_context_destroy(context8); + } + + // After this update, back to the original behaviour + ddwaf_handle handle9; + { + auto exclusions = readRule(R"({rules_data: []})"); + handle9 = ddwaf_update(handle8, &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } + + ASSERT_NE(handle9, nullptr); + + { + ddwaf_context context8 = ddwaf_context_init(handle8); + ASSERT_NE(context8, nullptr); + + ddwaf_context context9 = ddwaf_context_init(handle9); + ASSERT_NE(context9, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "server.request.query", ddwaf_object_string(&tmp, "rule3")); + + ddwaf_result result8; + ddwaf_result result9; + + EXPECT_EQ(ddwaf_run(context8, ¶meter, &result8, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context9, ¶meter, &result9, LONG_TIME), DDWAF_MATCH); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result8.actions.size, 0); + EXPECT_EQ(result9.actions.size, 0); + + ddwaf_result_free(&result8); + ddwaf_result_free(&result9); + + ddwaf_context_destroy(context8); + ddwaf_context_destroy(context9); + } + + { + ddwaf_context context8 = ddwaf_context_init(handle8); + ASSERT_NE(context8, nullptr); + + ddwaf_context context9 = ddwaf_context_init(handle9); + ASSERT_NE(context9, nullptr); + + ddwaf_object tmp; + ddwaf_object parameter = DDWAF_OBJECT_MAP; + ddwaf_object_map_add( + ¶meter, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); + + ddwaf_result result8; + ddwaf_result result9; + + EXPECT_EQ(ddwaf_run(context8, ¶meter, &result8, LONG_TIME), DDWAF_MATCH); + EXPECT_EQ(ddwaf_run(context9, ¶meter, &result9, LONG_TIME), DDWAF_OK); + + ddwaf_object_free(¶meter); + + EXPECT_EQ(result9.actions.size, 0); + + ddwaf_result_free(&result8); + ddwaf_result_free(&result9); + + ddwaf_context_destroy(context8); + ddwaf_context_destroy(context9); + } + + for (auto *handle : {handle1, handle2, handle3, handle4, handle6, handle7, handle8, handle9}) { + uint32_t size; + const char *const *addresses = ddwaf_required_addresses(handle, &size); + EXPECT_EQ(size, 4); + + std::set available_addresses{"http.client_ip", "server.request.query", + "server.request.params", "server.response.status"}; + while ((size--) != 0U) { + EXPECT_NE(available_addresses.find(addresses[size]), available_addresses.end()); + } + } + + for (auto *handle : {handle5}) { + uint32_t size; + const char *const *addresses = ddwaf_required_addresses(handle, &size); + EXPECT_EQ(size, 3); + + // While the ruleset contains 2 addresses, an existing object filter + // forces server.request.query to be kept + std::set available_addresses{ + "http.client_ip", "usr.id", "server.request.query"}; + while ((size--) != 0U) { + EXPECT_NE(available_addresses.find(addresses[size]), available_addresses.end()); + } + } + + ddwaf_destroy(handle9); + ddwaf_destroy(handle8); + ddwaf_destroy(handle7); + ddwaf_destroy(handle6); + ddwaf_destroy(handle5); + ddwaf_destroy(handle4); + ddwaf_destroy(handle3); + ddwaf_destroy(handle2); + ddwaf_destroy(handle1); } diff --git a/tests/main.cpp b/tests/main.cpp index 69117ffbb..efe70ae24 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -35,7 +35,7 @@ void log_cb(DDWAF_LOG_LEVEL level, const char *function, const char *file, unsig int main(int argc, char *argv[]) { - ddwaf_set_log_cb(log_cb, DDWAF_LOG_WARN); + ddwaf_set_log_cb(log_cb, DDWAF_LOG_TRACE); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/tests/manifest_test.cpp b/tests/manifest_test.cpp index edfae8415..86d3aaa82 100644 --- a/tests/manifest_test.cpp +++ b/tests/manifest_test.cpp @@ -8,205 +8,127 @@ TEST(TestManifest, TestEmpty) { - auto manifest = ddwaf::manifest_builder().build_manifest(); - EXPECT_FALSE(manifest.contains("path")); - EXPECT_TRUE(manifest.empty()); - - auto [res, id] = manifest.get_target("path"); - EXPECT_FALSE(res); - EXPECT_EQ(id, 0); + ddwaf::manifest manifest; + auto opt_target = manifest.find("path"); + EXPECT_FALSE(opt_target); } TEST(TestManifest, TestBasic) { - ddwaf::manifest_builder mb; - auto target = mb.insert("path", {}); + ddwaf::manifest manifest; + auto target = manifest.insert("path"); { - auto opt_target = mb.find("path"); + auto opt_target = manifest.find("path"); EXPECT_TRUE(opt_target.has_value()); EXPECT_EQ(*opt_target, target); } // Test double insertion - EXPECT_EQ(target, mb.insert("path", {})); - - auto manifest = mb.build_manifest(); - - EXPECT_TRUE(manifest.contains("path")); - EXPECT_FALSE(manifest.empty()); - - auto [res, id] = manifest.get_target("path"); - auto info = manifest.get_target_info(id); - EXPECT_TRUE(info.key_path.empty()); - EXPECT_STREQ(info.name.c_str(), "path"); - EXPECT_EQ(target, id); + EXPECT_EQ(target, manifest.insert("path")); - // This is it's own root address - EXPECT_EQ(manifest::get_root(id), id); - - auto &addresses = manifest.get_root_addresses(); + const auto &addresses = manifest.get_root_addresses(); EXPECT_EQ(addresses.size(), 1); EXPECT_STREQ(addresses[0], "path"); } TEST(TestManifest, TestMultipleAddrs) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::map targets; for (const std::string str : {"path0", "path1", "path2", "path3"}) { - auto target = mb.insert(str, {}); + auto target = manifest.insert(str); targets[str] = target; // Test double insertion - EXPECT_EQ(target, mb.insert(str, {})); + EXPECT_EQ(target, manifest.insert(str)); - auto opt_target = mb.find(str); + auto opt_target = manifest.find(str); EXPECT_TRUE(opt_target.has_value()); EXPECT_EQ(*opt_target, target); } - auto manifest = mb.build_manifest(); - - for (const std::string str : {"path0", "path1", "path2", "path3"}) { - EXPECT_TRUE(manifest.contains(str)); - - auto [res, id] = manifest.get_target(str); - auto info = manifest.get_target_info(id); - EXPECT_TRUE(info.key_path.empty()); - EXPECT_EQ(targets[str], id); + { + // The first call should generate the root addresses + const auto &addresses = manifest.get_root_addresses(); + EXPECT_EQ(addresses.size(), 4); - // This is it's own root address - EXPECT_EQ(manifest::get_root(id), id); - } - - auto &addresses = manifest.get_root_addresses(); - EXPECT_EQ(addresses.size(), 4); - - for (const std::string str : {"path0", "path1", "path2", "path3"}) { - EXPECT_NE(find(addresses.begin(), addresses.end(), str), addresses.end()); + for (const std::string str : {"path0", "path1", "path2", "path3"}) { + EXPECT_NE(find(addresses.begin(), addresses.end(), str), addresses.end()); + } } } -TEST(TestManifest, TestBasicKeyPath) +TEST(TestManifest, TestUpdateTargets) { - ddwaf::manifest_builder mb; - std::vector key_path{"key_path"}; - - auto target = mb.insert("path", key_path); + ddwaf::manifest manifest; + for (const std::string str : {"path0", "path1", "path2", "path3"}) { manifest.insert(str); } - // Test double insertion - EXPECT_EQ(target, mb.insert("path", key_path)); - - auto manifest = mb.build_manifest(); - - EXPECT_TRUE(manifest.contains("path")); - EXPECT_FALSE(manifest.empty()); - - auto info = manifest.get_target_info(target); - EXPECT_TRUE(info.key_path == key_path); - EXPECT_STREQ(info.name.c_str(), "path"); - - auto [res, root_id] = manifest.get_target("path"); - EXPECT_EQ(manifest::get_root(target), root_id); - - auto &addresses = manifest.get_root_addresses(); - EXPECT_EQ(addresses.size(), 1); - EXPECT_STREQ(addresses[0], "path"); -} + { + std::unordered_set targets; + targets.emplace(*manifest.find("path0")); + targets.emplace(*manifest.find("path1")); + targets.emplace(*manifest.find("path2")); + targets.emplace(*manifest.find("path3")); + + // After this, no targets should be removed + manifest.remove_unused(targets); + + EXPECT_TRUE(manifest.find("path0")); + EXPECT_TRUE(manifest.find("path1")); + EXPECT_TRUE(manifest.find("path2")); + EXPECT_TRUE(manifest.find("path3")); + } -TEST(TestManifest, TestMultipleAddrsKeyPath) -{ - ddwaf::manifest_builder mb; + { + std::unordered_set targets; + targets.emplace(*manifest.find("path0")); + targets.emplace(*manifest.find("path2")); - std::vector key_path{"key_path"}; - std::map targets; - for (auto str : {"path0", "path1", "path2", "path3"}) { - auto target = mb.insert(str, key_path); - targets[str] = target; + // After this, only path0 and path2 should be in the manifest + manifest.remove_unused(targets); - // Test double insertion - EXPECT_EQ(target, mb.insert(str, key_path)); + EXPECT_TRUE(manifest.find("path0")); + EXPECT_FALSE(manifest.find("path1")); + EXPECT_TRUE(manifest.find("path2")); + EXPECT_FALSE(manifest.find("path3")); } - auto manifest = mb.build_manifest(); + { + // After this, the manifest should be empty + manifest.remove_unused({}); - for (auto &[name, id] : targets) { - auto [res, root_id] = manifest.get_target(name); - auto info = manifest.get_target_info(id); - EXPECT_TRUE(info.key_path == key_path); - EXPECT_EQ(manifest::get_root(id), root_id); - EXPECT_STREQ(info.name.c_str(), name.c_str()); - } - - auto &addresses = manifest.get_root_addresses(); - EXPECT_EQ(addresses.size(), 4); - for (const std::string str : {"path0", "path1", "path2", "path3"}) { - EXPECT_NE(find(addresses.begin(), addresses.end(), str), addresses.end()); + EXPECT_FALSE(manifest.find("path0")); + EXPECT_FALSE(manifest.find("path1")); + EXPECT_FALSE(manifest.find("path2")); + EXPECT_FALSE(manifest.find("path3")); } } -TEST(TestManifest, TestBasicMultiKeyPath) +TEST(TestManifest, TestRootAddresses) { - ddwaf::manifest_builder mb; - std::vector key_path{"first", "second", "last"}; - - auto target = mb.insert("path", key_path); - - // Test double insertion - EXPECT_EQ(target, mb.insert("path", key_path)); - - auto manifest = mb.build_manifest(); - - EXPECT_TRUE(manifest.contains("path")); - EXPECT_FALSE(manifest.empty()); - - auto info = manifest.get_target_info(target); - EXPECT_TRUE(info.key_path == key_path); - EXPECT_STREQ(info.name.c_str(), "path"); - - auto [res, root_id] = manifest.get_target("path"); - EXPECT_EQ(manifest::get_root(target), root_id); - - auto &addresses = manifest.get_root_addresses(); - EXPECT_EQ(addresses.size(), 1); - EXPECT_STREQ(addresses[0], "path"); -} + ddwaf::manifest manifest; -TEST(TestManifest, TestMultipleAddrsMultiKeyPath) -{ - ddwaf::manifest_builder mb; + for (const std::string str : {"path0", "path1", "path2", "path3"}) { manifest.insert(str); } - std::vector key_path{"first", "second", "last"}; - std::map targets; - for (auto str : {"path0", "path1", "path2", "path3"}) { - auto target = mb.insert(str, key_path); - targets[str] = target; + { + // The first call should generate the root addresses + const auto &addresses = manifest.get_root_addresses(); + EXPECT_EQ(addresses.size(), 4); - // Test double insertion - EXPECT_EQ(target, mb.insert(str, key_path)); + for (const std::string str : {"path0", "path1", "path2", "path3"}) { + EXPECT_NE(find(addresses.begin(), addresses.end(), str), addresses.end()); + } } - auto manifest = mb.build_manifest(); - - for (auto &[name, id] : targets) { - auto [res, root_id] = manifest.get_target(name); - auto info = manifest.get_target_info(id); - EXPECT_TRUE(info.key_path == key_path); - EXPECT_EQ(manifest::get_root(id), root_id); - EXPECT_STREQ(info.name.c_str(), name.c_str()); - } + { + // The second call should reuse the generated array + const auto &addresses = manifest.get_root_addresses(); + EXPECT_EQ(addresses.size(), 4); - auto &addresses = manifest.get_root_addresses(); - EXPECT_EQ(addresses.size(), 4); - for (const std::string str : {"path0", "path1", "path2", "path3"}) { - EXPECT_NE(find(addresses.begin(), addresses.end(), str), addresses.end()); + for (const std::string str : {"path0", "path1", "path2", "path3"}) { + EXPECT_NE(find(addresses.begin(), addresses.end(), str), addresses.end()); + } } } - -TEST(TestManifest, TestUnknownArgID) -{ - ddwaf::manifest manifest({}, {}); - EXPECT_FALSE(manifest.contains({})); -} diff --git a/tests/mkmap_test.cpp b/tests/mkmap_test.cpp new file mode 100644 index 000000000..d04b3fe57 --- /dev/null +++ b/tests/mkmap_test.cpp @@ -0,0 +1,138 @@ +// 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 "test.h" + +TEST(TestMultiKeyMap, Find) +{ + rule_tag_map ruledb; + + struct rule_spec { + std::string id; + std::string type; + std::string category; + std::unordered_map tags; + }; + + std::vector specs{{"id0", "type0", "category0", {{"key", "value0"}}}, + {"id1", "type0", "category0", {{"key", "value1"}}}, + {"id2", "type0", "category1", {{"key", "value0"}}}, + {"id3", "type0", "category1", {{"key", "value1"}}}, + {"id4", "type1", "category0", {{"key", "value0"}}}, + {"id5", "type1", "category0", {{"key", "value1"}}}, + {"id6", "type1", "category1", {{"key", "value0"}}}, + {"id7", "type1", "category1", {{"key", "value1"}}}}; + + 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( + std::string(spec.id), "name", decltype(tags)(tags), std::vector{}); + ruledb.insert(rule_ptr->tags, rule_ptr); + } + + using sv_pair = std::pair; + { + auto rules = ruledb.find(sv_pair{"type"sv, "type0"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"category", "category0"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(sv_pair{"key"sv, "value0"sv}); + EXPECT_EQ(rules.size(), 4); + } + + using s_pair = std::pair; + { + auto rules = ruledb.find(s_pair{"type"sv, "type1"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(s_pair{"category", "category1"}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.find(s_pair{"key"sv, "value1"sv}); + EXPECT_EQ(rules.size(), 4); + } +} + +TEST(TestMultiKeyMap, Multifind) +{ + rule_tag_map ruledb; + + struct rule_spec { + std::string id; + std::string type; + std::string category; + std::unordered_map tags; + }; + + std::vector specs{{"id0", "type0", "category0", {{"key", "value0"}}}, + {"id1", "type0", "category0", {{"key", "value1"}}}, + {"id2", "type0", "category1", {{"key", "value0"}}}, + {"id3", "type0", "category1", {{"key", "value1"}}}, + {"id4", "type1", "category0", {{"key", "value0"}}}, + {"id5", "type1", "category0", {{"key", "value1"}}}, + {"id6", "type1", "category1", {{"key", "value0"}}}, + {"id7", "type1", "category1", {{"key", "value1"}}}}; + + 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( + std::string(spec.id), "name", decltype(tags)(tags), std::vector{}); + ruledb.insert(rule_ptr->tags, rule_ptr); + } + + using sv_pair_vec = std::vector>; + { + auto rules = ruledb.multifind(sv_pair_vec{{"type", "type0"}}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.multifind(sv_pair_vec{{"category", "category0"}}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.multifind(sv_pair_vec{{"type", "type0"}, {"category", "category0"}}); + EXPECT_EQ(rules.size(), 2); + } + + { + auto rules = ruledb.multifind(sv_pair_vec{{"key", "value0"}}); + EXPECT_EQ(rules.size(), 4); + } + + { + auto rules = ruledb.multifind(sv_pair_vec{{"type", "type0"}, {"key", "value0"}}); + EXPECT_EQ(rules.size(), 2); + } + + { + auto rules = ruledb.multifind(sv_pair_vec{{"category", "category0"}, {"key", "value0"}}); + EXPECT_EQ(rules.size(), 2); + } + + { + auto rules = ruledb.multifind( + sv_pair_vec{{"type", "type0"}, {"category", "category0"}, {"key", "value0"}}); + EXPECT_EQ(rules.size(), 1); + } +} diff --git a/tests/object_filter_test.cpp b/tests/object_filter_test.cpp index 88dd3459c..8b68a1073 100644 --- a/tests/object_filter_test.cpp +++ b/tests/object_filter_test.cpp @@ -12,9 +12,9 @@ using namespace ddwaf::exclusion; TEST(TestObjectFilter, RootTarget) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + object_store store(manifest); ddwaf_object root, child, tmp; @@ -39,9 +39,9 @@ TEST(TestObjectFilter, RootTarget) TEST(TestObjectFilter, SingleTarget) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + object_store store(manifest); ddwaf_object root, child, tmp; @@ -66,10 +66,10 @@ TEST(TestObjectFilter, SingleTarget) TEST(TestObjectFilter, MultipleTargets) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto path_params = mb.insert("path_params", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto path_params = manifest.insert("path_params"); + object_store store(manifest); ddwaf_object root, child, sibling, object, tmp; @@ -110,11 +110,11 @@ TEST(TestObjectFilter, MultipleTargets) TEST(TestObjectFilter, MissingTarget) { - ddwaf::manifest_builder mb; - mb.insert("query", {}); - mb.insert("path_params", {}); - auto status = mb.insert("status", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + manifest.insert("query"); + manifest.insert("path_params"); + auto status = manifest.insert("status"); + object_store store(manifest); ddwaf_object root, child, sibling, object, tmp; @@ -151,9 +151,9 @@ TEST(TestObjectFilter, MissingTarget) TEST(TestObjectFilter, SingleTargetCache) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + object_store store(manifest); ddwaf_object root, child, tmp; @@ -184,10 +184,10 @@ TEST(TestObjectFilter, SingleTargetCache) TEST(TestObjectFilter, MultipleTargetsCache) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto path_params = mb.insert("path_params", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto path_params = manifest.insert("path_params"); + object_store store(manifest); object_filter filter; @@ -245,9 +245,8 @@ TEST(TestObjectFilter, MultipleTargetsCache) TEST(TestObjectFilter, SingleGlobTarget) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); object_filter filter; filter.insert(query, {"*"}); @@ -315,9 +314,8 @@ TEST(TestObjectFilter, SingleGlobTarget) TEST(TestObjectFilter, GlobAndKeyTarget) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); object_filter filter; filter.insert(query, {"*"}); @@ -386,9 +384,8 @@ TEST(TestObjectFilter, GlobAndKeyTarget) TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); object_filter filter; filter.insert(query, {"*", "value"}); @@ -495,9 +492,8 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) TEST(TestObjectFilter, MultipleGlobsTargets) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); object_filter filter; filter.insert(query, {"*", "*", "*"}); @@ -590,9 +586,8 @@ TEST(TestObjectFilter, MultipleGlobsTargets) TEST(TestObjectFilter, MultipleComponentsMultipleGlobAndKeyTargets) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); object_filter filter; filter.insert(query, {"a", "b", "c"}); @@ -656,9 +651,8 @@ TEST(TestObjectFilter, MultipleComponentsMultipleGlobAndKeyTargets) TEST(TestObjectFilter, ArrayWithGlobTargets) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); object_filter filter; filter.insert(query, {"a", "*", "c", "d"}); diff --git a/tests/object_store_test.cpp b/tests/object_store_test.cpp index 15b8ccdd2..629e5248a 100644 --- a/tests/object_store_test.cpp +++ b/tests/object_store_test.cpp @@ -10,12 +10,10 @@ using namespace ddwaf; TEST(TestObjectStore, InsertInvalidObject) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto query_key = mb.insert("query", {"key"}); - auto url = mb.insert("url", {}); - auto url_key = mb.insert("url", {"key"}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto url = manifest.insert("url"); + object_store store(manifest); ddwaf_object root = DDWAF_OBJECT_INVALID; @@ -25,19 +23,14 @@ TEST(TestObjectStore, InsertInvalidObject) EXPECT_FALSE((bool)store); EXPECT_FALSE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); - EXPECT_FALSE(store.is_new_target(query_key)); EXPECT_FALSE(store.is_new_target(url)); - EXPECT_FALSE(store.is_new_target(url_key)); EXPECT_EQ(store.get_target(query), nullptr); - EXPECT_EQ(store.get_target(query_key), nullptr); EXPECT_EQ(store.get_target(url), nullptr); - EXPECT_EQ(store.get_target(url_key), nullptr); } TEST(TestObjectStore, InsertMalformedMap) { - ddwaf::manifest_builder mb; - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; object_store store(manifest); @@ -51,13 +44,13 @@ TEST(TestObjectStore, InsertMalformedMap) TEST(TestObjectStore, InsertMalformedMapKey) { - ddwaf::manifest_builder mb; - mb.insert("key", {}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + manifest.insert("key"); object_store store(manifest); - ddwaf_object tmp, root = DDWAF_OBJECT_MAP; + ddwaf_object tmp; + ddwaf_object root = DDWAF_OBJECT_MAP; ddwaf_object_map_add(&root, "key", ddwaf_object_string(&tmp, "value")); free((void *)root.array[0].parameterName); @@ -69,12 +62,9 @@ TEST(TestObjectStore, InsertMalformedMapKey) TEST(TestObjectStore, InsertStringObject) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto query_key = mb.insert("query", {"key"}); - auto url = mb.insert("url", {}); - auto url_key = mb.insert("url", {"key"}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto url = manifest.insert("url"); object_store store(manifest); @@ -86,27 +76,21 @@ TEST(TestObjectStore, InsertStringObject) EXPECT_FALSE((bool)store); EXPECT_FALSE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); - EXPECT_FALSE(store.is_new_target(query_key)); EXPECT_FALSE(store.is_new_target(url)); - EXPECT_FALSE(store.is_new_target(url_key)); EXPECT_EQ(store.get_target(query), nullptr); - EXPECT_EQ(store.get_target(query_key), nullptr); EXPECT_EQ(store.get_target(url), nullptr); - EXPECT_EQ(store.get_target(url_key), nullptr); } TEST(TestObjectStore, InsertAndGetObject) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto query_key = mb.insert("query", {"key"}); - auto url = mb.insert("url", {}); - auto url_key = mb.insert("url", {"key"}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto url = manifest.insert("url"); object_store store(manifest); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "query", ddwaf_object_string(&tmp, "hello")); @@ -115,27 +99,23 @@ TEST(TestObjectStore, InsertAndGetObject) EXPECT_TRUE((bool)store); EXPECT_TRUE(store.has_new_targets()); EXPECT_TRUE(store.is_new_target(query)); - EXPECT_TRUE(store.is_new_target(query_key)); EXPECT_FALSE(store.is_new_target(url)); - EXPECT_FALSE(store.is_new_target(url_key)); EXPECT_NE(store.get_target(query), nullptr); - EXPECT_NE(store.get_target(query_key), nullptr); EXPECT_EQ(store.get_target(url), nullptr); - EXPECT_EQ(store.get_target(url_key), nullptr); } TEST(TestObjectStore, InsertMultipleUniqueObjects) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto query_key = mb.insert("query", {"key"}); - auto url = mb.insert("url", {}); - auto url_key = mb.insert("url", {"key"}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto url = manifest.insert("url"); object_store store(manifest); - ddwaf_object first, second, third, tmp; + ddwaf_object first; + ddwaf_object second; + ddwaf_object third; + ddwaf_object tmp; ddwaf_object_map(&first); ddwaf_object_map_add(&first, "query", ddwaf_object_string(&tmp, "hello")); @@ -144,13 +124,9 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) EXPECT_TRUE((bool)store); EXPECT_TRUE(store.has_new_targets()); EXPECT_TRUE(store.is_new_target(query)); - EXPECT_TRUE(store.is_new_target(query_key)); EXPECT_FALSE(store.is_new_target(url)); - EXPECT_FALSE(store.is_new_target(url_key)); EXPECT_NE(store.get_target(query), nullptr); - EXPECT_NE(store.get_target(query_key), nullptr); EXPECT_EQ(store.get_target(url), nullptr); - EXPECT_EQ(store.get_target(url_key), nullptr); ddwaf_object_map(&second); ddwaf_object_map_add(&second, "url", ddwaf_object_string(&tmp, "hello")); @@ -160,40 +136,32 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) EXPECT_TRUE((bool)store); EXPECT_TRUE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); - EXPECT_FALSE(store.is_new_target(query_key)); EXPECT_TRUE(store.is_new_target(url)); - EXPECT_TRUE(store.is_new_target(url_key)); EXPECT_NE(store.get_target(query), nullptr); - EXPECT_NE(store.get_target(query_key), nullptr); EXPECT_NE(store.get_target(url), nullptr); - EXPECT_NE(store.get_target(url_key), nullptr); third = DDWAF_OBJECT_INVALID; store.insert(third); EXPECT_TRUE((bool)store); EXPECT_FALSE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); - EXPECT_FALSE(store.is_new_target(query_key)); EXPECT_FALSE(store.is_new_target(url)); - EXPECT_FALSE(store.is_new_target(url_key)); EXPECT_NE(store.get_target(query), nullptr); - EXPECT_NE(store.get_target(query_key), nullptr); EXPECT_NE(store.get_target(url), nullptr); - EXPECT_NE(store.get_target(url_key), nullptr); } TEST(TestObjectStore, InsertMultipleOverlappingObjects) { - ddwaf::manifest_builder mb; - auto query = mb.insert("query", {}); - auto query_key = mb.insert("query", {"key"}); - auto url = mb.insert("url", {}); - auto url_key = mb.insert("url", {"key"}); - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + auto query = manifest.insert("query"); + auto url = manifest.insert("url"); object_store store(manifest); - ddwaf_object first, second, third, tmp; + ddwaf_object first; + ddwaf_object second; + ddwaf_object third; + ddwaf_object tmp; ddwaf_object_map(&first); ddwaf_object_map_add(&first, "query", ddwaf_object_string(&tmp, "hello")); store.insert(first); @@ -201,13 +169,9 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_TRUE((bool)store); EXPECT_TRUE(store.has_new_targets()); EXPECT_TRUE(store.is_new_target(query)); - EXPECT_TRUE(store.is_new_target(query_key)); EXPECT_FALSE(store.is_new_target(url)); - EXPECT_FALSE(store.is_new_target(url_key)); EXPECT_NE(store.get_target(query), nullptr); - EXPECT_NE(store.get_target(query_key), nullptr); EXPECT_EQ(store.get_target(url), nullptr); - EXPECT_EQ(store.get_target(url_key), nullptr); { const ddwaf_object *object = store.get_target(query); @@ -216,13 +180,6 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_STREQ(object->stringValue, "hello"); } - { - const ddwaf_object *object = store.get_target(query_key); - EXPECT_NE(object, nullptr); - EXPECT_EQ(object->type, DDWAF_OBJ_STRING); - EXPECT_STREQ(object->stringValue, "hello"); - } - // Reinsert query ddwaf_object_map(&second); ddwaf_object_map_add(&second, "url", ddwaf_object_string(&tmp, "hello")); @@ -232,9 +189,7 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_TRUE((bool)store); EXPECT_TRUE(store.has_new_targets()); EXPECT_TRUE(store.is_new_target(query)); - EXPECT_TRUE(store.is_new_target(query_key)); EXPECT_TRUE(store.is_new_target(url)); - EXPECT_TRUE(store.is_new_target(url_key)); { const ddwaf_object *object = store.get_target(url); @@ -243,13 +198,6 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_STREQ(object->stringValue, "hello"); } - { - const ddwaf_object *object = store.get_target(url_key); - EXPECT_NE(object, nullptr); - EXPECT_EQ(object->type, DDWAF_OBJ_STRING); - EXPECT_STREQ(object->stringValue, "hello"); - } - { const ddwaf_object *object = store.get_target(query); EXPECT_NE(object, nullptr); @@ -257,13 +205,6 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_STREQ(object->stringValue, "bye"); } - { - const ddwaf_object *object = store.get_target(query_key); - EXPECT_NE(object, nullptr); - EXPECT_EQ(object->type, DDWAF_OBJ_STRING); - EXPECT_STREQ(object->stringValue, "bye"); - } - // Reinsert url ddwaf_object_map(&third); ddwaf_object_map_add(&third, "url", ddwaf_object_string(&tmp, "bye")); @@ -272,11 +213,8 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_TRUE((bool)store); EXPECT_TRUE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); - EXPECT_FALSE(store.is_new_target(query_key)); EXPECT_TRUE(store.is_new_target(url)); - EXPECT_TRUE(store.is_new_target(url_key)); EXPECT_NE(store.get_target(query), nullptr); - EXPECT_NE(store.get_target(query_key), nullptr); { const ddwaf_object *object = store.get_target(url); @@ -284,11 +222,4 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) EXPECT_EQ(object->type, DDWAF_OBJ_STRING); EXPECT_STREQ(object->stringValue, "bye"); } - - { - const ddwaf_object *object = store.get_target(url_key); - EXPECT_NE(object, nullptr); - EXPECT_EQ(object->type, DDWAF_OBJ_STRING); - EXPECT_STREQ(object->stringValue, "bye"); - } } diff --git a/tests/parameter_test.cpp b/tests/parameter_test.cpp index 5b6da34ac..7e44c0fcc 100644 --- a/tests/parameter_test.cpp +++ b/tests/parameter_test.cpp @@ -12,7 +12,7 @@ TEST(TestParameter, ToBool) ddwaf_object root; ddwaf_object_bool(&root, true); - bool value = ddwaf::parameter(root); + bool value = static_cast(parameter(root)); EXPECT_TRUE(value); } @@ -20,7 +20,7 @@ TEST(TestParameter, ToBool) ddwaf_object root; ddwaf_object_bool(&root, false); - bool value = ddwaf::parameter(root); + bool value = static_cast(parameter(root)); EXPECT_FALSE(value); } @@ -28,7 +28,7 @@ TEST(TestParameter, ToBool) ddwaf_object root; ddwaf_object_string(&root, "true"); - bool value = ddwaf::parameter(root); + bool value = static_cast(parameter(root)); EXPECT_TRUE(value); ddwaf_object_free(&root); @@ -38,7 +38,7 @@ TEST(TestParameter, ToBool) ddwaf_object root; ddwaf_object_string(&root, "TrUe"); - bool value = ddwaf::parameter(root); + bool value = static_cast(parameter(root)); EXPECT_TRUE(value); ddwaf_object_free(&root); @@ -48,7 +48,7 @@ TEST(TestParameter, ToBool) ddwaf_object root; ddwaf_object_string(&root, "false"); - bool value = ddwaf::parameter(root); + bool value = static_cast(parameter(root)); EXPECT_FALSE(value); ddwaf_object_free(&root); @@ -58,7 +58,7 @@ TEST(TestParameter, ToBool) ddwaf_object root; ddwaf_object_string(&root, "FaLsE"); - bool value = ddwaf::parameter(root); + bool value = static_cast(parameter(root)); EXPECT_FALSE(value); ddwaf_object_free(&root); @@ -78,7 +78,7 @@ TEST(TestParameter, ToUint64) ddwaf_object root; ddwaf_object_unsigned_force(&root, 2123); - uint64_t value = ddwaf::parameter(root); + uint64_t value = static_cast(parameter(root)); EXPECT_EQ(value, 2123); } @@ -86,7 +86,7 @@ TEST(TestParameter, ToUint64) ddwaf_object root; ddwaf_object_unsigned(&root, 2123); - uint64_t value = ddwaf::parameter(root); + uint64_t value = static_cast(parameter(root)); EXPECT_EQ(value, 2123); ddwaf_object_free(&root); @@ -96,7 +96,7 @@ TEST(TestParameter, ToUint64) ddwaf_object root; ddwaf_object_string(&root, "2123"); - uint64_t value = ddwaf::parameter(root); + uint64_t value = static_cast(parameter(root)); EXPECT_EQ(value, 2123); ddwaf_object_free(&root); @@ -116,7 +116,7 @@ TEST(TestParameter, ToInt64) ddwaf_object root; ddwaf_object_signed_force(&root, -2123); - int64_t value = ddwaf::parameter(root); + int64_t value = static_cast(parameter(root)); EXPECT_EQ(value, -2123); } @@ -124,7 +124,7 @@ TEST(TestParameter, ToInt64) ddwaf_object root; ddwaf_object_signed(&root, -2123); - int64_t value = ddwaf::parameter(root); + int64_t value = static_cast(parameter(root)); EXPECT_EQ(value, -2123); ddwaf_object_free(&root); @@ -134,7 +134,7 @@ TEST(TestParameter, ToInt64) ddwaf_object root; ddwaf_object_string(&root, "-2123"); - int64_t value = ddwaf::parameter(root); + int64_t value = static_cast(parameter(root)); EXPECT_EQ(value, -2123); ddwaf_object_free(&root); @@ -154,7 +154,7 @@ TEST(TestParameter, ToString) ddwaf_object root; ddwaf_object_string(&root, "hello world, this is a string"); - std::string value = ddwaf::parameter(root); + auto value = static_cast(ddwaf::parameter(root)); EXPECT_STREQ(value.c_str(), "hello world, this is a string"); ddwaf_object_free(&root); @@ -174,7 +174,7 @@ TEST(TestParameter, ToStringView) ddwaf_object root; ddwaf_object_string(&root, "hello world, this is a string"); - std::string_view value = ddwaf::parameter(root); + auto value = static_cast(ddwaf::parameter(root)); EXPECT_STREQ(value.data(), "hello world, this is a string"); ddwaf_object_free(&root); @@ -198,7 +198,7 @@ TEST(TestParameter, ToVector) ddwaf_object_array_add(&root, ddwaf_object_string(&tmp, std::to_string(i).c_str())); } - ddwaf::parameter::vector vec_param = ddwaf::parameter(root); + auto vec_param = static_cast(ddwaf::parameter(root)); EXPECT_EQ(vec_param.size(), 20); unsigned i = 0; @@ -229,7 +229,7 @@ TEST(TestParameter, ToMap) ddwaf_object_string(&tmp, std::to_string(i + 100).c_str())); } - ddwaf::parameter::map map_param = ddwaf::parameter(root); + auto map_param = static_cast(ddwaf::parameter(root)); EXPECT_EQ(map_param.size(), 20); for (unsigned i = 0; i < 20; i++) { @@ -259,7 +259,7 @@ TEST(TestParameter, ToStringVector) ddwaf_object_array_add(&root, ddwaf_object_string(&tmp, std::to_string(i).c_str())); } - std::vector vec_param = ddwaf::parameter(root); + auto vec_param = static_cast>(ddwaf::parameter(root)); EXPECT_EQ(vec_param.size(), 20); unsigned i = 0; @@ -298,7 +298,7 @@ TEST(TestParameter, ToStringViewVector) ddwaf_object_array_add(&root, ddwaf_object_string(&tmp, std::to_string(i).c_str())); } - std::vector vec_param = ddwaf::parameter(root); + auto vec_param = static_cast>(ddwaf::parameter(root)); EXPECT_EQ(vec_param.size(), 20); unsigned i = 0; @@ -337,7 +337,7 @@ TEST(TestParameter, ToStringViewSet) ddwaf_object_array_add(&root, ddwaf_object_string(&tmp, std::to_string(i).c_str())); } - ddwaf::parameter::string_set set_param = ddwaf::parameter(root); + auto set_param = static_cast(ddwaf::parameter(root)); EXPECT_EQ(set_param.size(), 20); for (unsigned i = 0; i < 20; i++) { diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp deleted file mode 100644 index 92e63359d..000000000 --- a/tests/parser_test.cpp +++ /dev/null @@ -1,452 +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 "test.h" - -void run_test(ddwaf_handle handle) -{ - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object param, arg2, tmp; - ddwaf_object_map(¶m); - ddwaf_object_map(&arg2); - - ddwaf_object_map_add(¶m, "arg1", ddwaf_object_string(&tmp, "string 1")); - ddwaf_object_map_add(&arg2, "x", ddwaf_object_string(&tmp, "string 2")); - ddwaf_object_map_add(&arg2, "y", ddwaf_object_string(&tmp, "string 3")); - ddwaf_object_map_add(¶m, "arg2", &arg2); - - ddwaf_result ret; - - // Run with just arg1 - auto code = ddwaf_run(context, ¶m, &ret, LONG_TIME); - EXPECT_EQ(code, DDWAF_MATCH); - EXPECT_FALSE(ret.timeout); - EXPECT_STREQ(ret.data, - R"([{"rule":{"id":"1","name":"rule1","tags":{"type":"flow1","category":"category1"}},"rule_matches":[{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg1","key_path":[],"value":"string 1","highlight":["string 1"]}]},{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg2","key_path":["x"],"value":"string 2","highlight":["string 2"]}]},{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg2","key_path":["y"],"value":"string 3","highlight":["string 3"]}]}]}])"); - ddwaf_result_free(&ret); - - ddwaf_context_destroy(context); -} - -TEST(TestParserV1, Basic) -{ - auto rule = readRule( - 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: .*}}]}]})"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - EXPECT_EQ(info.loaded, 1); - EXPECT_EQ(info.failed, 0); - EXPECT_EQ(info.version, nullptr); - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 0); - ddwaf_ruleset_info_free(&info); - - run_test(handle); - - ddwaf_destroy(handle); -} - -TEST(TestParserV2, Basic) -{ - auto rule = readRule( - R"({version: '2.1', metadata: {rules_version: '1.2.7'}, rules: [{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}]})"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - EXPECT_EQ(info.loaded, 1); - EXPECT_EQ(info.failed, 0); - EXPECT_STREQ(info.version, "1.2.7"); - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 0); - ddwaf_ruleset_info_free(&info); - - run_test(handle); - - ddwaf_destroy(handle); -} - -TEST(TestParserV1, TestInvalidRule) -{ - auto rule = readFile("invalid_single_v1.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - - EXPECT_EQ(info.failed, 1); - EXPECT_EQ(info.loaded, 0); - - ddwaf_ruleset_info_free(&info); -} - -TEST(TestParserV1, TestMultipleSameInvalidRules) -{ - auto rule = readFile("invalid_multiple_same_v1.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 2); - EXPECT_NE(rules.find("1"), rules.end()); - EXPECT_NE(rules.find("2"), rules.end()); - - EXPECT_EQ(info.failed, 2); - EXPECT_EQ(info.loaded, 0); - - ddwaf_ruleset_info_free(&info); -} - -TEST(TestParserV1, TestMultipleDiffInvalidRules) -{ - auto rule = readFile("invalid_multiple_diff_v1.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 2); - - { - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - } - - { - auto it = errors.find("unknown processor: squash"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("2"), rules.end()); - } - - EXPECT_EQ(info.failed, 2); - EXPECT_EQ(info.loaded, 0); - - ddwaf_ruleset_info_free(&info); -} - -TEST(TestParserV1, TestMultipleMixInvalidRules) -{ - auto rule = readFile("invalid_multiple_mix_v1.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 3); - - { - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 2); - EXPECT_NE(rules.find("1"), rules.end()); - EXPECT_NE(rules.find("3"), rules.end()); - } - - { - auto it = errors.find("unknown processor: squash"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("2"), rules.end()); - } - - { - auto it = errors.find("missing key 'inputs'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("4"), rules.end()); - } - - EXPECT_EQ(info.failed, 4); - EXPECT_EQ(info.loaded, 1); - - ddwaf_ruleset_info_free(&info); - - ddwaf_destroy(handle); -} - -TEST(TestParserV1, TestInvalidDuplicate) -{ - auto rule = readFile("invalid_duplicate_v1.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("duplicate rule"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - - EXPECT_EQ(info.failed, 1); - EXPECT_EQ(info.loaded, 1); - - ddwaf_ruleset_info_free(&info); - - ddwaf_destroy(handle); -} - -TEST(TestParserV2, TestInvalidRule) -{ - auto rule = readFile("invalid_single.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - - EXPECT_EQ(info.failed, 1); - EXPECT_EQ(info.loaded, 0); - - ddwaf_ruleset_info_free(&info); -} - -TEST(TestParserV2, TestMultipleSameInvalidRules) -{ - auto rule = readFile("invalid_multiple_same.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 2); - EXPECT_NE(rules.find("1"), rules.end()); - EXPECT_NE(rules.find("2"), rules.end()); - - EXPECT_EQ(info.failed, 2); - EXPECT_EQ(info.loaded, 0); - - ddwaf_ruleset_info_free(&info); -} - -TEST(TestParserV2, TestMultipleDiffInvalidRules) -{ - auto rule = readFile("invalid_multiple_diff.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 2); - - { - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - } - - { - auto it = errors.find("unknown processor: squash"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("2"), rules.end()); - } - - EXPECT_EQ(info.failed, 2); - EXPECT_EQ(info.loaded, 0); - - ddwaf_ruleset_info_free(&info); -} - -TEST(TestParserV2, TestMultipleMixInvalidRules) -{ - auto rule = readFile("invalid_multiple_mix.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 3); - - { - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 2); - EXPECT_NE(rules.find("1"), rules.end()); - EXPECT_NE(rules.find("3"), rules.end()); - } - - { - auto it = errors.find("unknown processor: squash"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("2"), rules.end()); - } - - { - auto it = errors.find("missing key 'inputs'"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("4"), rules.end()); - } - - EXPECT_EQ(info.failed, 4); - EXPECT_EQ(info.loaded, 1); - - ddwaf_ruleset_info_free(&info); - - ddwaf_destroy(handle); -} - -TEST(TestParserV2, TestInvalidDuplicate) -{ - auto rule = readFile("invalid_duplicate.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("duplicate rule"); - EXPECT_NE(it, errors.end()); - - ddwaf::parameter::string_set rules = it->second; - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - - EXPECT_EQ(info.failed, 1); - EXPECT_EQ(info.loaded, 1); - - ddwaf_ruleset_info_free(&info); - - ddwaf_destroy(handle); -} - -TEST(TestParserV2, TestInvalidRuleset) -{ - auto rule = readFile("invalid_ruleset.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_ruleset_info info; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); - ASSERT_EQ(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf::parameter::map errors = parameter(info.errors); - EXPECT_EQ(errors.size(), 20); - - EXPECT_EQ(info.failed, 400); - EXPECT_EQ(info.loaded, 0); - - for (auto &[key, value] : errors) { - ddwaf::parameter::vector rules = parameter(value); - EXPECT_EQ(rules.size(), 20); - } - ddwaf_ruleset_info_free(&info); - - ddwaf_destroy(handle); -} diff --git a/tests/parser_v1_test.cpp b/tests/parser_v1_test.cpp new file mode 100644 index 000000000..0b50d7027 --- /dev/null +++ b/tests/parser_v1_test.cpp @@ -0,0 +1,230 @@ +// 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 "test.h" + +static void run_test(ddwaf_handle handle) +{ + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object param, arg2, tmp; + ddwaf_object_map(¶m); + ddwaf_object_map(&arg2); + + ddwaf_object_map_add(¶m, "arg1", ddwaf_object_string(&tmp, "string 1")); + ddwaf_object_map_add(&arg2, "x", ddwaf_object_string(&tmp, "string 2")); + ddwaf_object_map_add(&arg2, "y", ddwaf_object_string(&tmp, "string 3")); + ddwaf_object_map_add(¶m, "arg2", &arg2); + + ddwaf_result ret; + + // Run with just arg1 + auto code = ddwaf_run(context, ¶m, &ret, LONG_TIME); + EXPECT_EQ(code, DDWAF_MATCH); + EXPECT_FALSE(ret.timeout); + EXPECT_STREQ(ret.data, + R"([{"rule":{"id":"1","name":"rule1","tags":{"type":"flow1","category":"category1"}},"rule_matches":[{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg1","key_path":[],"value":"string 1","highlight":["string 1"]}]},{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg2","key_path":["x"],"value":"string 2","highlight":["string 2"]}]},{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg2","key_path":["y"],"value":"string 3","highlight":["string 3"]}]}]}])"); + ddwaf_result_free(&ret); + + ddwaf_context_destroy(context); +} + +TEST(TestParserV1, Basic) +{ + auto rule = readRule( + 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: .*}}]}]})"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + EXPECT_EQ(info.loaded, 1); + EXPECT_EQ(info.failed, 0); + EXPECT_EQ(info.version, nullptr); + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 0); + ddwaf_ruleset_info_free(&info); + + run_test(handle); + + ddwaf_destroy(handle); +} + +TEST(TestParserV1, TestInvalidRule) +{ + auto rule = readFile("invalid_single_v1.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + + EXPECT_EQ(info.failed, 1); + EXPECT_EQ(info.loaded, 0); + + ddwaf_ruleset_info_free(&info); +} + +TEST(TestParserV1, TestMultipleSameInvalidRules) +{ + auto rule = readFile("invalid_multiple_same_v1.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 2); + EXPECT_NE(rules.find("1"), rules.end()); + EXPECT_NE(rules.find("2"), rules.end()); + + EXPECT_EQ(info.failed, 2); + EXPECT_EQ(info.loaded, 0); + + ddwaf_ruleset_info_free(&info); +} + +TEST(TestParserV1, TestMultipleDiffInvalidRules) +{ + auto rule = readFile("invalid_multiple_diff_v1.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 2); + + { + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + } + + { + auto it = errors.find("unknown processor: squash"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("2"), rules.end()); + } + + EXPECT_EQ(info.failed, 2); + EXPECT_EQ(info.loaded, 0); + + ddwaf_ruleset_info_free(&info); +} + +TEST(TestParserV1, TestMultipleMixInvalidRules) +{ + auto rule = readFile("invalid_multiple_mix_v1.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 3); + + { + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 2); + EXPECT_NE(rules.find("1"), rules.end()); + EXPECT_NE(rules.find("3"), rules.end()); + } + + { + auto it = errors.find("unknown processor: squash"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("2"), rules.end()); + } + + { + auto it = errors.find("missing key 'inputs'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("4"), rules.end()); + } + + EXPECT_EQ(info.failed, 4); + EXPECT_EQ(info.loaded, 1); + + ddwaf_ruleset_info_free(&info); + + ddwaf_destroy(handle); +} + +TEST(TestParserV1, TestInvalidDuplicate) +{ + auto rule = readFile("invalid_duplicate_v1.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("duplicate rule"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + + EXPECT_EQ(info.failed, 1); + EXPECT_EQ(info.loaded, 1); + + ddwaf_ruleset_info_free(&info); + + ddwaf_destroy(handle); +} diff --git a/tests/parser_v2_input_filters.cpp b/tests/parser_v2_input_filters.cpp new file mode 100644 index 000000000..7a8b0afab --- /dev/null +++ b/tests/parser_v2_input_filters.cpp @@ -0,0 +1,67 @@ +// 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 "test.h" + +TEST(TestParserV2InputFilters, ParseFilterWithoutID) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule(R"([{inputs: [{address: http.client_ip}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 0); + EXPECT_EQ(exclusions.input_filters.size(), 0); +} + +TEST(TestParserV2InputFilters, ParseDuplicateFilters) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + manifest.insert("http.client_ip"); + manifest.insert("usr.id"); + + auto object = readRule( + R"([{id: 1, inputs: [{address: http.client_ip}]}, {id: 1, inputs: [{address: usr.id}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 0); + EXPECT_EQ(exclusions.input_filters.size(), 1); +} + +TEST(TestParserV2InputFilters, ParseNoConditionsOrTargets) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + manifest.insert("http.client_ip"); + manifest.insert("usr.id"); + + auto object = readRule(R"([{id: 1, inputs: [{address: http.client_ip}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 0); + EXPECT_EQ(exclusions.input_filters.size(), 1); + + const auto &exclusion_it = exclusions.input_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 0); + EXPECT_TRUE(exclusion.filter); +} + +// TODO more tests diff --git a/tests/parser_v2_interface_test.cpp b/tests/parser_v2_interface_test.cpp new file mode 100644 index 000000000..254c251f7 --- /dev/null +++ b/tests/parser_v2_interface_test.cpp @@ -0,0 +1,256 @@ +// 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 "test.h" + +static void run_test(ddwaf_handle handle) +{ + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object param, arg2, tmp; + ddwaf_object_map(¶m); + ddwaf_object_map(&arg2); + + ddwaf_object_map_add(¶m, "arg1", ddwaf_object_string(&tmp, "string 1")); + ddwaf_object_map_add(&arg2, "x", ddwaf_object_string(&tmp, "string 2")); + ddwaf_object_map_add(&arg2, "y", ddwaf_object_string(&tmp, "string 3")); + ddwaf_object_map_add(¶m, "arg2", &arg2); + + ddwaf_result ret; + + // Run with just arg1 + auto code = ddwaf_run(context, ¶m, &ret, LONG_TIME); + EXPECT_EQ(code, DDWAF_MATCH); + EXPECT_FALSE(ret.timeout); + EXPECT_STREQ(ret.data, + R"([{"rule":{"id":"1","name":"rule1","tags":{"type":"flow1","category":"category1"}},"rule_matches":[{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg1","key_path":[],"value":"string 1","highlight":["string 1"]}]},{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg2","key_path":["x"],"value":"string 2","highlight":["string 2"]}]},{"operator":"match_regex","operator_value":".*","parameters":[{"address":"arg2","key_path":["y"],"value":"string 3","highlight":["string 3"]}]}]}])"); + ddwaf_result_free(&ret); + + ddwaf_context_destroy(context); +} + +TEST(TestParserV2Interface, Basic) +{ + auto rule = readRule( + R"({version: '2.1', metadata: {rules_version: '1.2.7'}, rules: [{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}]})"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + EXPECT_EQ(info.loaded, 1); + EXPECT_EQ(info.failed, 0); + EXPECT_STREQ(info.version, "1.2.7"); + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 0); + ddwaf_ruleset_info_free(&info); + + run_test(handle); + + ddwaf_destroy(handle); +} + +TEST(TestParserV2Interface, TestInvalidRule) +{ + auto rule = readFile("invalid_single.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + + EXPECT_EQ(info.failed, 1); + EXPECT_EQ(info.loaded, 0); + + ddwaf_ruleset_info_free(&info); +} + +TEST(TestParserV2Interface, TestMultipleSameInvalidRules) +{ + auto rule = readFile("invalid_multiple_same.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 2); + EXPECT_NE(rules.find("1"), rules.end()); + EXPECT_NE(rules.find("2"), rules.end()); + + EXPECT_EQ(info.failed, 2); + EXPECT_EQ(info.loaded, 0); + + ddwaf_ruleset_info_free(&info); +} + +TEST(TestParserV2Interface, TestMultipleDiffInvalidRules) +{ + auto rule = readFile("invalid_multiple_diff.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 2); + + { + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + } + + { + auto it = errors.find("unknown processor: squash"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("2"), rules.end()); + } + + EXPECT_EQ(info.failed, 2); + EXPECT_EQ(info.loaded, 0); + + ddwaf_ruleset_info_free(&info); +} + +TEST(TestParserV2Interface, TestMultipleMixInvalidRules) +{ + auto rule = readFile("invalid_multiple_mix.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 3); + + { + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 2); + EXPECT_NE(rules.find("1"), rules.end()); + EXPECT_NE(rules.find("3"), rules.end()); + } + + { + auto it = errors.find("unknown processor: squash"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("2"), rules.end()); + } + + { + auto it = errors.find("missing key 'inputs'"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("4"), rules.end()); + } + + EXPECT_EQ(info.failed, 4); + EXPECT_EQ(info.loaded, 1); + + ddwaf_ruleset_info_free(&info); + + ddwaf_destroy(handle); +} + +TEST(TestParserV2Interface, TestInvalidDuplicate) +{ + auto rule = readFile("invalid_duplicate.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("duplicate rule"); + EXPECT_NE(it, errors.end()); + + auto rules = static_cast(it->second); + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + + EXPECT_EQ(info.failed, 1); + EXPECT_EQ(info.loaded, 1); + + ddwaf_ruleset_info_free(&info); + + ddwaf_destroy(handle); +} + +TEST(TestParserV2Interface, TestInvalidRuleset) +{ + auto rule = readFile("invalid_ruleset.yaml"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_ruleset_info info; + + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &info); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + auto errors = static_cast(parameter(info.errors)); + EXPECT_EQ(errors.size(), 20); + + EXPECT_EQ(info.failed, 400); + EXPECT_EQ(info.loaded, 0); + + for (auto &[key, value] : errors) { + auto rules = static_cast(parameter(value)); + EXPECT_EQ(rules.size(), 20); + } + ddwaf_ruleset_info_free(&info); + + ddwaf_destroy(handle); +} diff --git a/tests/parser_v2_rule_filters.cpp b/tests/parser_v2_rule_filters.cpp new file mode 100644 index 000000000..f2bb2dff8 --- /dev/null +++ b/tests/parser_v2_rule_filters.cpp @@ -0,0 +1,316 @@ +// 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 "test.h" + +TEST(TestParserV2RuleFilters, ParseEmptyFilter) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule(R"([{id: 1}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 0); + EXPECT_EQ(exclusions.input_filters.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseFilterWithoutID) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule(R"([{rules_target: [{rule_id: 2939}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 0); + EXPECT_EQ(exclusions.input_filters.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseDuplicateUnconditionalRuleFilters) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseUnconditionalRuleFilterTargetID) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule(R"([{id: 1, rules_target: [{rule_id: 2939}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseUnconditionalRuleFilterTargetTags) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule(R"([{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::tags); + EXPECT_TRUE(target.rule_id.empty()); + EXPECT_EQ(target.tags.size(), 2); + EXPECT_STR(target.tags.find("type")->second, "rule"); + EXPECT_STR(target.tags.find("category")->second, "unknown"); +} + +TEST(TestParserV2RuleFilters, ParseUnconditionalRuleFilterTargetPriority) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseUnconditionalRuleFilterMultipleTargets) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939},{tags: {type: rule, category: unknown}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 2); + + { + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); + } + + { + const auto &target = exclusion.targets[1]; + EXPECT_EQ(target.type, parser::target_type::tags); + EXPECT_TRUE(target.rule_id.empty()); + EXPECT_EQ(target.tags.size(), 2); + EXPECT_STR(target.tags.find("type")->second, "rule"); + EXPECT_STR(target.tags.find("category")->second, "unknown"); + } +} + +TEST(TestParserV2RuleFilters, ParseMultipleUnconditionalRuleFilters) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 2, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 2); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + { + const auto &exclusion_it = exclusions.rule_filters.find("1"); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); + } + + { + const auto &exclusion_it = exclusions.rule_filters.find("2"); + EXPECT_STR(exclusion_it->first, "2"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 0); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::tags); + EXPECT_TRUE(target.rule_id.empty()); + EXPECT_EQ(target.tags.size(), 2); + EXPECT_STR(target.tags.find("type")->second, "rule"); + EXPECT_STR(target.tags.find("category")->second, "unknown"); + } +} + +TEST(TestParserV2RuleFilters, ParseDuplicateConditionalRuleFilters) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]},{id: 1, rules_target: [{tags: {type: rule, category: unknown}}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseConditionalRuleFilterSingleCondition) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 1); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseConditionalRuleFilterGlobal) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 1); + EXPECT_EQ(exclusion.targets.size(), 0); +} + +TEST(TestParserV2RuleFilters, ParseConditionalRuleFilterMultipleConditions) +{ + ddwaf::manifest manifest; + ddwaf::object_limits limits; + + auto object = readRule( + R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}])"); + auto exclusions_array = static_cast(parameter(object)); + auto exclusions = parser::v2::parse_filters(exclusions_array, manifest, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(exclusions.rule_filters.size(), 1); + EXPECT_EQ(exclusions.input_filters.size(), 0); + + const auto &exclusion_it = exclusions.rule_filters.begin(); + EXPECT_STR(exclusion_it->first, "1"); + + const auto &exclusion = exclusion_it->second; + EXPECT_EQ(exclusion.conditions.size(), 3); + EXPECT_EQ(exclusion.targets.size(), 1); + + const auto &target = exclusion.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} diff --git a/tests/parser_v2_rules_data_test.cpp b/tests/parser_v2_rules_data_test.cpp new file mode 100644 index 000000000..f3dc1e008 --- /dev/null +++ b/tests/parser_v2_rules_data_test.cpp @@ -0,0 +1,126 @@ +// 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 "parser/parser.hpp" +#include "test.h" + +TEST(TestParserV2RuleData, ParseIPData) +{ + std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + + auto object = readRule( + R"([{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 1); + EXPECT_STRV(rule_data["ip_data"]->name(), "ip_match"); +} + +TEST(TestParserV2RuleData, ParseStringData) +{ + std::unordered_map rule_data_ids{{"usr_data", "exact_match"}}; + + auto object = readRule( + R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 1); + EXPECT_STRV(rule_data["usr_data"]->name(), "exact_match"); +} + +TEST(TestParserV2RuleData, ParseMultipleRuleData) +{ + std::unordered_map rule_data_ids{ + {"ip_data", "ip_match"}, {"usr_data", "exact_match"}}; + + auto object = readRule( + R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 2); + EXPECT_STRV(rule_data["usr_data"]->name(), "exact_match"); + EXPECT_STRV(rule_data["ip_data"]->name(), "ip_match"); +} + +TEST(TestParserV2RuleData, ParseUnknownRuleData) +{ + std::unordered_map rule_data_ids{{"usr_data", "exact_match"}}; + + auto object = readRule( + R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 2); + EXPECT_STRV(rule_data["ip_data"]->name(), "ip_match"); + EXPECT_STRV(rule_data["usr_data"]->name(), "exact_match"); +} + +TEST(TestParserV2RuleData, ParseUnsupportedProcessor) +{ + std::unordered_map rule_data_ids{ + {"usr_data", "match_regex"}, {"ip_data", "phrase_match"}}; + + auto object = readRule( + R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 0); +} + +TEST(TestParserV2RuleData, ParseMissingType) +{ + std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + + auto object = readRule(R"([{id: ip_data, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 0); +} + +TEST(TestParserV2RuleData, ParseMissingID) +{ + std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + + auto object = + readRule(R"([{type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 0); +} + +TEST(TestParserV2RuleData, ParseMissingData) +{ + std::unordered_map rule_data_ids{{"ip_data", "ip_match"}}; + + auto object = readRule(R"([{id: ip_data, type: ip_with_expiration}])"); + auto input = static_cast(parameter(object)); + + auto rule_data = parser::v2::parse_rule_data(input, rule_data_ids); + ddwaf_object_free(&object); + + EXPECT_EQ(rule_data.size(), 0); +} diff --git a/tests/parser_v2_rules_override_test.cpp b/tests/parser_v2_rules_override_test.cpp new file mode 100644 index 000000000..2a90abd51 --- /dev/null +++ b/tests/parser_v2_rules_override_test.cpp @@ -0,0 +1,86 @@ +// 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 "test.h" + +TEST(TestParserV2RulesOverride, ParseRuleOverride) +{ + auto object = readRule(R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block]}])"); + + auto override_array = static_cast(parameter(object)); + auto overrides = parser::v2::parse_overrides(override_array); + ddwaf_object_free(&object); + + EXPECT_EQ(overrides.by_ids.size(), 0); + EXPECT_EQ(overrides.by_tags.size(), 1); + + auto &ovrd = overrides.by_tags[0]; + EXPECT_FALSE(ovrd.enabled.has_value()); + EXPECT_TRUE(ovrd.actions.has_value()); + EXPECT_EQ(ovrd.actions->size(), 1); + EXPECT_STR((*ovrd.actions)[0], "block"); + EXPECT_EQ(ovrd.targets.size(), 1); + + auto &target = ovrd.targets[0]; + EXPECT_EQ(target.type, parser::target_type::tags); + EXPECT_TRUE(target.rule_id.empty()); + EXPECT_EQ(target.tags.size(), 1); + EXPECT_STR(target.tags["confidence"], "1"); +} + +TEST(TestParserV2RulesOverride, ParseMultipleRuleOverrides) +{ + auto object = readRule( + R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block]},{rules_target: [{rule_id: 1}], enabled: false}])"); + + auto override_array = static_cast(parameter(object)); + auto overrides = parser::v2::parse_overrides(override_array); + ddwaf_object_free(&object); + + EXPECT_EQ(overrides.by_ids.size(), 1); + EXPECT_EQ(overrides.by_tags.size(), 1); + + { + auto &ovrd = overrides.by_tags[0]; + EXPECT_FALSE(ovrd.enabled.has_value()); + EXPECT_TRUE(ovrd.actions.has_value()); + EXPECT_EQ(ovrd.actions->size(), 1); + EXPECT_STR((*ovrd.actions)[0], "block"); + EXPECT_EQ(ovrd.targets.size(), 1); + + auto &target = ovrd.targets[0]; + EXPECT_EQ(target.type, parser::target_type::tags); + EXPECT_TRUE(target.rule_id.empty()); + EXPECT_EQ(target.tags.size(), 1); + // EXPECT_EQ(target.tags[0], {"confidence","1"}); + } + + { + auto &ovrd = overrides.by_ids[0]; + EXPECT_TRUE(ovrd.enabled.has_value()); + EXPECT_FALSE(*ovrd.enabled); + EXPECT_FALSE(ovrd.actions.has_value()); + EXPECT_EQ(ovrd.targets.size(), 1); + + auto &target = ovrd.targets[0]; + EXPECT_EQ(target.type, parser::target_type::id); + EXPECT_STR(target.rule_id, "1"); + EXPECT_EQ(target.tags.size(), 0); + } +} + +TEST(TestParserV2RulesOverride, ParseInconsistentRuleOverride) +{ + auto object = readRule( + R"([{rules_target: [{tags: {confidence: 1}}, {rule_id: 1}], on_match: [block], enabled: false}])"); + + auto override_array = static_cast(parameter(object)); + auto overrides = parser::v2::parse_overrides(override_array); + ddwaf_object_free(&object); + + EXPECT_EQ(overrides.by_ids.size(), 0); + EXPECT_EQ(overrides.by_tags.size(), 0); +} diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp new file mode 100644 index 000000000..f3dc758d6 --- /dev/null +++ b/tests/parser_v2_rules_test.cpp @@ -0,0 +1,188 @@ +// 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 "test.h" + +TEST(TestParserV2Rules, ParseRule) +{ + ddwaf::object_limits limits; + ruleset_info info; + ddwaf::manifest manifest; + std::unordered_map rule_data_ids; + + auto rule_object = readRule( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + auto rules = parser::v2::parse_rules(rule_array, info, manifest, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + + parser::rule_spec &rule = rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.conditions.size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags["type"], "flow1"); + EXPECT_STR(rule.tags["category"], "category1"); +} + +TEST(TestParserV2Rules, ParseRuleWithoutType) +{ + ddwaf::object_limits limits; + ruleset_info info; + ddwaf::manifest manifest; + std::unordered_map rule_data_ids; + + auto rule_object = readRule( + R"([{id: 1, name: rule1, tags: {category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + auto rules = parser::v2::parse_rules(rule_array, info, manifest, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + EXPECT_EQ(rules.size(), 0); +} + +TEST(TestParserV2Rules, ParseRuleWithoutID) +{ + ddwaf::object_limits limits; + ruleset_info info; + ddwaf::manifest manifest; + std::unordered_map rule_data_ids; + + auto rule_object = readRule( + R"([{name: rule1, tags: {type: type1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + auto rules = parser::v2::parse_rules(rule_array, info, manifest, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + EXPECT_EQ(rules.size(), 0); +} + +TEST(TestParserV2Rules, ParseMultipleRules) +{ + ddwaf::object_limits limits; + ruleset_info info; + ddwaf::manifest manifest; + std::unordered_map rule_data_ids; + + auto rule_object = readRule( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]},{id: secondrule, name: rule2, tags: {type: flow2, category: category2, confidence: none}, conditions: [{operator: ip_match, parameters: {inputs: [{address: http.client_ip}], data: blocked_ips}}], on_match: [block]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 2); + + auto rules = parser::v2::parse_rules(rule_array, info, manifest, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + EXPECT_EQ(rules.size(), 2); + EXPECT_NE(rules.find("1"), rules.end()); + EXPECT_NE(rules.find("secondrule"), rules.end()); + + { + parser::rule_spec &rule = rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.conditions.size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags["type"], "flow1"); + EXPECT_STR(rule.tags["category"], "category1"); + } + + { + parser::rule_spec &rule = rules["secondrule"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.conditions.size(), 1); + EXPECT_EQ(rule.actions.size(), 1); + EXPECT_STR(rule.actions[0], "block"); + EXPECT_STR(rule.name, "rule2"); + EXPECT_EQ(rule.tags.size(), 3); + EXPECT_STR(rule.tags["type"], "flow2"); + EXPECT_STR(rule.tags["category"], "category2"); + EXPECT_STR(rule.tags["confidence"], "none"); + } +} + +TEST(TestParserV2Rules, ParseMultipleRulesOneInvalid) +{ + ddwaf::object_limits limits; + ruleset_info info; + ddwaf::manifest manifest; + std::unordered_map rule_data_ids; + + auto rule_object = readRule( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]},{id: secondrule, name: rule2, tags: {type: flow2, category: category2, confidence: none}, conditions: [{operator: ip_match, parameters: {inputs: [{address: http.client_ip}], data: blocked_ips}}], on_match: [block]}, {id: error}])"); + + auto rule_array = static_cast(parameter(rule_object)); + + auto rules = parser::v2::parse_rules(rule_array, info, manifest, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + EXPECT_EQ(rules.size(), 2); + EXPECT_NE(rules.find("1"), rules.end()); + EXPECT_NE(rules.find("secondrule"), rules.end()); + + { + parser::rule_spec &rule = rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.conditions.size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags["type"], "flow1"); + EXPECT_STR(rule.tags["category"], "category1"); + } + + { + parser::rule_spec &rule = rules["secondrule"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.conditions.size(), 1); + EXPECT_EQ(rule.actions.size(), 1); + EXPECT_STR(rule.actions[0], "block"); + EXPECT_STR(rule.name, "rule2"); + EXPECT_EQ(rule.tags.size(), 3); + EXPECT_STR(rule.tags["type"], "flow2"); + EXPECT_STR(rule.tags["category"], "category2"); + EXPECT_STR(rule.tags["confidence"], "none"); + } +} + +TEST(TestParserV2Rules, ParseMultipleRulesOneDuplicate) +{ + ddwaf::object_limits limits; + ruleset_info info; + ddwaf::manifest manifest; + std::unordered_map rule_data_ids; + + auto rule_object = readRule( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x]}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [y]}], regex: .*}}]},{id: 1, name: rule2, tags: {type: flow2, category: category2, confidence: none}, conditions: [{operator: ip_match, parameters: {inputs: [{address: http.client_ip}], data: blocked_ips}}], on_match: [block]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 2); + + auto rules = parser::v2::parse_rules(rule_array, info, manifest, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + EXPECT_EQ(rules.size(), 1); + EXPECT_NE(rules.find("1"), rules.end()); + + { + parser::rule_spec &rule = rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.conditions.size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags["type"], "flow1"); + EXPECT_STR(rule.tags["category"], "category1"); + } +} diff --git a/tests/rule_data_dispatcher_test.cpp b/tests/rule_data_dispatcher_test.cpp deleted file mode 100644 index 4b20ab9f4..000000000 --- a/tests/rule_data_dispatcher_test.cpp +++ /dev/null @@ -1,779 +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 "test.h" - -using namespace ddwaf; - -namespace ddwaf { -namespace rule_processor { -class mock_processor : public base { -public: - using rule_data_type = std::vector; - - mock_processor() = default; - explicit mock_processor(const rule_data_type &list) { (void)list; } - ~mock_processor() override = default; - - std::string_view name() const override { return "mock_processor"; } - - std::optional match(std::string_view str) const override - { - (void)str; - return {}; - } -}; - -} // namespace rule_processor -namespace parser { - -using data_type = std::vector; - -template <> data_type parse_rule_data(std::string_view type, parameter &input) -{ - (void)type; - return input; -} - -} // namespace parser - -} // namespace ddwaf - -TEST(TestRuleDataDispatcher, InterfaceNullHandle) -{ - EXPECT_EQ(ddwaf_update_rule_data(nullptr, nullptr), DDWAF_ERR_INVALID_ARGUMENT); -} - -TEST(TestRuleDataDispatcher, InterfaceNullObject) -{ - auto rule = readFile("rule_data.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - EXPECT_EQ(ddwaf_update_rule_data(handle, nullptr), DDWAF_ERR_INVALID_ARGUMENT); - ddwaf_destroy(handle); -} - -TEST(TestRuleDataDispatcher, InterfaceInvalidObject) -{ - auto rule = readFile("rule_data.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf_object root; - ddwaf_object_map(&root); - EXPECT_EQ(ddwaf_update_rule_data(handle, &root), DDWAF_ERR_INVALID_OBJECT); - ddwaf_object_free(&root); - - ddwaf_destroy(handle); -} - -TEST(TestRuleDataDispatcher, Interface) -{ - auto rule = readFile("rule_data.yaml"); - 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); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - { - ddwaf_object root, ips, user, data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf_object_map(&ips); - ddwaf_object_map_add(&ips, "type", ddwaf_object_string(&tmp, "ip_with_expiration")); - ddwaf_object_map_add(&ips, "id", ddwaf_object_string(&tmp, "ip_data")); - ddwaf_object_map_add(&ips, "data", &data); - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "paco")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf_object_map(&user); - ddwaf_object_map_add(&user, "type", ddwaf_object_string(&tmp, "data_with_expiration")); - ddwaf_object_map_add(&user, "id", ddwaf_object_string(&tmp, "usr_data")); - ddwaf_object_map_add(&user, "data", &data); - - ddwaf_object_array(&root); - ddwaf_object_array_add(&root, &ips); - ddwaf_object_array_add(&root, &user); - - EXPECT_EQ(ddwaf_update_rule_data(handle, &root), DDWAF_OK); - ddwaf_object_free(&root); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_MATCH); - - ddwaf_context_destroy(context); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_MATCH); - - ddwaf_context_destroy(context); - } - - ddwaf_destroy(handle); -} - -TEST(TestRuleDataDispatcher, InterfacePreloadData) -{ - auto rule = readFile("rule_data_with_data.yaml"); - 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); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_MATCH); - - ddwaf_context_destroy(context); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_MATCH); - - ddwaf_context_destroy(context); - } - - { - ddwaf_object root, ips, user, data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.2")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf_object_map(&ips); - ddwaf_object_map_add(&ips, "type", ddwaf_object_string(&tmp, "ip_with_expiration")); - ddwaf_object_map_add(&ips, "id", ddwaf_object_string(&tmp, "ip_data")); - ddwaf_object_map_add(&ips, "data", &data); - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "pepe")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf_object_map(&user); - ddwaf_object_map_add(&user, "type", ddwaf_object_string(&tmp, "data_with_expiration")); - ddwaf_object_map_add(&user, "id", ddwaf_object_string(&tmp, "usr_data")); - ddwaf_object_map_add(&user, "data", &data); - - ddwaf_object_array(&root); - ddwaf_object_array_add(&root, &ips); - ddwaf_object_array_add(&root, &user); - - EXPECT_EQ(ddwaf_update_rule_data(handle, &root), DDWAF_OK); - ddwaf_object_free(&root); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - ddwaf_destroy(handle); -} - -TEST(TestRuleDataDispatcher, InterfaceInvalidUserData) -{ - auto rule = readFile("rule_data.yaml"); - 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); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - { - ddwaf_object root, ips, user, data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf_object_map(&ips); - ddwaf_object_map_add(&ips, "type", ddwaf_object_string(&tmp, "ip_with_expiration")); - ddwaf_object_map_add(&ips, "id", ddwaf_object_string(&tmp, "ip_data")); - ddwaf_object_map_add(&ips, "data", &data); - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "paco")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf_object_map(&user); - // Missing type - ddwaf_object_map_add(&user, "id", ddwaf_object_string(&tmp, "usr_data")); - ddwaf_object_map_add(&user, "data", &data); - - ddwaf_object_array(&root); - ddwaf_object_array_add(&root, &ips); - ddwaf_object_array_add(&root, &user); - - EXPECT_EQ(ddwaf_update_rule_data(handle, &root), DDWAF_OK); - ddwaf_object_free(&root); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_MATCH); - - ddwaf_context_destroy(context); - } - - { - // The user data update was invalid, so it'll be ignored - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - ddwaf_destroy(handle); -} - -TEST(TestRuleDataDispatcher, Basic) -{ - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); - - auto manifest = mb.build_manifest(); - - auto cond = std::make_shared(std::move(targets), std::vector{}, - std::make_unique(), ddwaf::object_limits(), - condition::data_source::values, true); - - rule_data::dispatcher dispatcher; - - dispatcher.register_condition("id", cond); - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond->match(store, manifest, {}, true, deadline); - EXPECT_FALSE(match.has_value()); - } - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - dispatcher.dispatch("id", "ip_with_expiration", param); - - ddwaf_object_free(&data); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond->match(store, manifest, {}, true, deadline); - EXPECT_TRUE(match.has_value()); - - EXPECT_STREQ(match->resolved.c_str(), "192.168.1.1"); - EXPECT_STREQ(match->matched.c_str(), "192.168.1.1"); - EXPECT_STREQ(match->operator_name.data(), "ip_match"); - EXPECT_STREQ(match->source.data(), "http.client_ip"); - EXPECT_TRUE(match->key_path.empty()); - } -} - -TEST(TestRuleDataDispatcher, MultipleProcessors) -{ - ddwaf::manifest_builder mb; - auto client_ip_target = mb.insert("http.client_ip", {}); - auto usr_id_target = mb.insert("usr.id", {}); - auto manifest = mb.build_manifest(); - - auto cond1 = std::make_shared(std::vector{client_ip_target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - auto cond2 = std::make_shared(std::vector{usr_id_target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - rule_data::dispatcher dispatcher; - - dispatcher.register_condition("ip_data", cond1); - dispatcher.register_condition("usr_data", cond2); - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond1->match(store, manifest, {}, true, deadline); - EXPECT_FALSE(match.has_value()); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond2->match(store, manifest, {}, true, deadline); - EXPECT_FALSE(match.has_value()); - } - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - dispatcher.dispatch("ip_data", "ip_with_expiration", param); - - ddwaf_object_free(&data); - } - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "paco")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - dispatcher.dispatch("usr_data", "data_with_expiration", param); - - ddwaf_object_free(&data); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond1->match(store, manifest, {}, true, deadline); - EXPECT_TRUE(match.has_value()); - - EXPECT_STREQ(match->resolved.c_str(), "192.168.1.1"); - EXPECT_STREQ(match->matched.c_str(), "192.168.1.1"); - EXPECT_STREQ(match->operator_name.data(), "ip_match"); - EXPECT_STREQ(match->source.data(), "http.client_ip"); - EXPECT_TRUE(match->key_path.empty()); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond2->match(store, manifest, {}, true, deadline); - EXPECT_TRUE(match.has_value()); - - EXPECT_STREQ(match->resolved.c_str(), "paco"); - EXPECT_STREQ(match->matched.c_str(), "paco"); - EXPECT_STREQ(match->operator_name.data(), "exact_match"); - EXPECT_STREQ(match->source.data(), "usr.id"); - EXPECT_TRUE(match->key_path.empty()); - } -} - -TEST(TestRuleDataDispatcher, MultipleProcessorTypesSameID) -{ - ddwaf::manifest_builder mb; - auto client_ip_target = mb.insert("http.client_ip", {}); - auto usr_id_target = mb.insert("usr.id", {}); - auto manifest = mb.build_manifest(); - - auto cond1 = std::make_shared(std::vector{client_ip_target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - auto cond2 = std::make_shared(std::vector{usr_id_target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - rule_data::dispatcher dispatcher; - - EXPECT_NO_THROW(dispatcher.register_condition("id", cond1)); - EXPECT_NO_THROW(dispatcher.register_condition("id", cond2)); -} - -TEST(TestRuleDataDispatcher, ConflictingProcessorTypesSameID) -{ - ddwaf::manifest_builder mb; - auto target = mb.insert("http.client_ip", {}); - auto manifest = mb.build_manifest(); - - auto cond1 = std::make_shared(std::vector{target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - auto cond2 = std::make_shared(std::vector{target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - rule_data::dispatcher dispatcher; - - dispatcher.register_condition("id", cond1); - EXPECT_THROW( - dispatcher.register_condition("id", cond2), std::bad_cast); -} - -TEST(TestRuleDataDispatcher, UnkonwnID) -{ - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); - - auto manifest = mb.build_manifest(); - - auto cond = std::make_shared(std::move(targets), std::vector{}, - std::make_unique(), ddwaf::object_limits(), - condition::data_source::values, true); - - rule_data::dispatcher dispatcher; - - dispatcher.register_condition("id", cond); - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - dispatcher.dispatch("unknown", "ip_with_expiration", param); - ddwaf_object_free(&data); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond->match(store, manifest, {}, true, deadline); - EXPECT_FALSE(match.has_value()); - } -} - -TEST(TestRuleDataDispatcher, ImmutableCondition) -{ - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); - - auto manifest = mb.build_manifest(); - - auto cond = std::make_shared(std::move(targets), std::vector{}, - std::make_unique()); - - rule_data::dispatcher dispatcher; - - dispatcher.register_condition("id", cond); - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - EXPECT_THROW(dispatcher.dispatch("id", "ip_with_expiration", param), std::runtime_error); - - ddwaf_object_free(&data); - } -} - -TEST(TestRuleDataDispatcherBuilder, Basic) -{ - ddwaf::manifest_builder mb; - auto client_ip_target = mb.insert("http.client_ip", {}); - auto usr_id_target = mb.insert("usr.id", {}); - auto manifest = mb.build_manifest(); - - auto cond1 = std::make_shared(std::vector{client_ip_target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - auto cond2 = std::make_shared(std::vector{usr_id_target}, - std::vector{}, std::make_unique(), - ddwaf::object_limits(), condition::data_source::values, true); - - rule_data::dispatcher dispatcher; - dispatcher.register_condition("ip_data", cond1); - dispatcher.register_condition("usr_data", cond2); - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "192.168.1.1")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - dispatcher.dispatch("ip_data", "ip_with_expiration", param); - - ddwaf_object_free(&data); - } - - { - ddwaf_object data, data_point, tmp; - - ddwaf_object_map(&data_point); - ddwaf_object_map_add(&data_point, "value", ddwaf_object_string(&tmp, "paco")); - ddwaf_object_map_add(&data_point, "expiration", ddwaf_object_string(&tmp, "0")); - - ddwaf_object_array(&data); - ddwaf_object_array_add(&data, &data_point); - - ddwaf::parameter param = data; - dispatcher.dispatch("usr_data", "data_with_expiration", param); - - ddwaf_object_free(&data); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "http.client_ip", ddwaf_object_string(&tmp, "192.168.1.1")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond1->match(store, manifest, {}, true, deadline); - EXPECT_TRUE(match.has_value()); - - EXPECT_STREQ(match->resolved.c_str(), "192.168.1.1"); - EXPECT_STREQ(match->matched.c_str(), "192.168.1.1"); - EXPECT_STREQ(match->operator_name.data(), "ip_match"); - EXPECT_STREQ(match->source.data(), "http.client_ip"); - EXPECT_TRUE(match->key_path.empty()); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "usr.id", ddwaf_object_string(&tmp, "paco")); - - ddwaf::object_store store(manifest); - store.insert(root); - - ddwaf::timer deadline{2s}; - - auto match = cond2->match(store, manifest, {}, true, deadline); - EXPECT_TRUE(match.has_value()); - - EXPECT_STREQ(match->resolved.c_str(), "paco"); - EXPECT_STREQ(match->matched.c_str(), "paco"); - EXPECT_STREQ(match->operator_name.data(), "exact_match"); - EXPECT_STREQ(match->source.data(), "usr.id"); - EXPECT_TRUE(match->key_path.empty()); - } -} diff --git a/tests/rule_filter_test.cpp b/tests/rule_filter_test.cpp index c040ea709..a4a4ff9cb 100644 --- a/tests/rule_filter_test.cpp +++ b/tests/rule_filter_test.cpp @@ -10,12 +10,10 @@ using namespace ddwaf; TEST(TestRuleFilter, Match) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); - - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); @@ -23,7 +21,7 @@ TEST(TestRuleFilter, Match) std::vector> conditions{std::move(cond)}; ddwaf::exclusion::rule_filter filter{"filter", std::move(conditions), - {std::make_shared(ddwaf::rule("", "", "", "", {}))}}; + {std::make_shared(ddwaf::rule("", "", {}, {}))}}; ddwaf_object root, tmp; ddwaf_object_map(&root); @@ -35,18 +33,16 @@ TEST(TestRuleFilter, Match) ddwaf::timer deadline{2s}; ddwaf::exclusion::rule_filter::cache_type cache; - auto rules = filter.match(store, manifest, cache, deadline); + auto rules = filter.match(store, cache, deadline); EXPECT_FALSE(rules.empty()); } TEST(TestRuleFilter, NoMatch) { - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{})); @@ -65,17 +61,17 @@ TEST(TestRuleFilter, NoMatch) ddwaf::timer deadline{2s}; ddwaf::exclusion::rule_filter::cache_type cache; - EXPECT_TRUE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_TRUE(filter.match(store, cache, deadline).empty()); } TEST(TestRuleFilter, ValidateCachedMatch) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -83,16 +79,15 @@ TEST(TestRuleFilter, ValidateCachedMatch) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); ddwaf::exclusion::rule_filter filter{"filter", std::move(conditions), - {std::make_shared(ddwaf::rule("", "", "", "", {}))}}; + {std::make_shared(ddwaf::rule("", "", {}, {}))}}; ddwaf::exclusion::rule_filter::cache_type cache; @@ -108,7 +103,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_TRUE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_TRUE(filter.match(store, cache, deadline).empty()); } { @@ -120,18 +115,18 @@ TEST(TestRuleFilter, ValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_FALSE(filter.match(store, cache, deadline).empty()); } } TEST(TestRuleFilter, MatchWithoutCache) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -139,16 +134,15 @@ TEST(TestRuleFilter, MatchWithoutCache) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); ddwaf::exclusion::rule_filter filter{"filter", std::move(conditions), - {std::make_shared(ddwaf::rule("", "", "", "", {}))}}; + {std::make_shared(ddwaf::rule("", "", {}, {}))}}; // 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 @@ -163,7 +157,7 @@ TEST(TestRuleFilter, MatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_TRUE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_TRUE(filter.match(store, cache, deadline).empty()); } { @@ -175,18 +169,18 @@ TEST(TestRuleFilter, MatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_FALSE(filter.match(store, cache, deadline).empty()); } } TEST(TestRuleFilter, NoMatchWithoutCache) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -194,14 +188,13 @@ TEST(TestRuleFilter, NoMatchWithoutCache) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); ddwaf::exclusion::rule_filter filter{"filter", std::move(conditions), {}}; // In this test we validate that when the cache is empty and only one @@ -216,7 +209,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_TRUE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_TRUE(filter.match(store, cache, deadline).empty()); } { @@ -229,18 +222,18 @@ TEST(TestRuleFilter, NoMatchWithoutCache) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_TRUE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_TRUE(filter.match(store, cache, deadline).empty()); } } TEST(TestRuleFilter, FullCachedMatchSecondRun) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -248,16 +241,15 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); ddwaf::exclusion::rule_filter filter{"filter", std::move(conditions), - {std::make_shared(ddwaf::rule("", "", "", "", {}))}}; + {std::make_shared(ddwaf::rule("", "", {}, {}))}}; ddwaf::object_store store(manifest); ddwaf::exclusion::rule_filter::cache_type cache; @@ -273,7 +265,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_FALSE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_FALSE(filter.match(store, cache, deadline).empty()); EXPECT_TRUE(cache.result); } @@ -285,7 +277,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - EXPECT_TRUE(filter.match(store, manifest, cache, deadline).empty()); + EXPECT_TRUE(filter.match(store, cache, deadline).empty()); } } diff --git a/tests/rule_test.cpp b/tests/rule_test.cpp index 512f0af1a..ea826395f 100644 --- a/tests/rule_test.cpp +++ b/tests/rule_test.cpp @@ -10,20 +10,19 @@ using namespace ddwaf; TEST(TestRule, Match) { - std::vector targets; + std::vector targets; - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); - - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; ddwaf::rule rule( - "id", "name", "type", "category", std::move(conditions), {"update", "block", "passlist"}); + "id", "name", std::move(tags), std::move(conditions), {"update", "block", "passlist"}); ddwaf_object root, tmp; ddwaf_object_map(&root); @@ -35,7 +34,7 @@ TEST(TestRule, Match) ddwaf::timer deadline{2s}; rule::cache_type cache; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); EXPECT_STREQ(event->id.data(), "id"); @@ -57,19 +56,17 @@ TEST(TestRule, Match) TEST(TestRule, NoMatch) { - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{})); std::vector> conditions{std::move(cond)}; - - ddwaf::rule rule("id", "name", "type", "category", std::move(conditions)); + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + ddwaf::rule rule("id", "name", std::move(tags), std::move(conditions)); ddwaf_object root, tmp; ddwaf_object_map(&root); @@ -81,18 +78,18 @@ TEST(TestRule, NoMatch) ddwaf::timer deadline{2s}; rule::cache_type cache; - auto match = rule.match(store, manifest, cache, {}, deadline); + auto match = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(match.has_value()); } TEST(TestRule, ValidateCachedMatch) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -100,15 +97,16 @@ TEST(TestRule, ValidateCachedMatch) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - ddwaf::rule rule("id", "name", "type", "category", std::move(conditions)); + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + ddwaf::rule rule("id", "name", std::move(tags), std::move(conditions)); ddwaf::rule::cache_type cache; // To validate that the cache works, we pass an object store containing @@ -123,7 +121,7 @@ TEST(TestRule, ValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } @@ -136,7 +134,7 @@ TEST(TestRule, ValidateCachedMatch) store.insert(root); ddwaf::timer deadline{2s}; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); EXPECT_STREQ(event->id.data(), "id"); EXPECT_STREQ(event->name.data(), "name"); @@ -168,12 +166,12 @@ TEST(TestRule, ValidateCachedMatch) TEST(TestRule, MatchWithoutCache) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -181,15 +179,16 @@ TEST(TestRule, MatchWithoutCache) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - ddwaf::rule rule("id", "name", "type", "category", std::move(conditions)); + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + ddwaf::rule rule("id", "name", std::move(tags), std::move(conditions)); // 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 @@ -204,7 +203,7 @@ TEST(TestRule, MatchWithoutCache) ddwaf::timer deadline{2s}; ddwaf::rule::cache_type cache; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } @@ -217,7 +216,7 @@ TEST(TestRule, MatchWithoutCache) ddwaf::timer deadline{2s}; ddwaf::rule::cache_type cache; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); { @@ -243,12 +242,12 @@ TEST(TestRule, MatchWithoutCache) TEST(TestRule, NoMatchWithoutCache) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -256,15 +255,16 @@ TEST(TestRule, NoMatchWithoutCache) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - ddwaf::rule rule("id", "name", "type", "category", std::move(conditions)); + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + ddwaf::rule rule("id", "name", std::move(tags), std::move(conditions)); // 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). @@ -278,7 +278,7 @@ TEST(TestRule, NoMatchWithoutCache) ddwaf::timer deadline{2s}; ddwaf::rule::cache_type cache; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } @@ -292,19 +292,19 @@ TEST(TestRule, NoMatchWithoutCache) ddwaf::timer deadline{2s}; ddwaf::rule::cache_type cache; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } } TEST(TestRule, FullCachedMatchSecondRun) { - ddwaf::manifest_builder mb; + ddwaf::manifest manifest; std::vector> conditions; { - std::vector targets; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique( std::vector{"192.168.0.1"})); @@ -312,15 +312,16 @@ TEST(TestRule, FullCachedMatchSecondRun) } { - std::vector targets; - targets.push_back(mb.insert("usr.id", {})); + std::vector targets; + targets.push_back({manifest.insert("usr.id"), "usr.id", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"admin"})); conditions.push_back(std::move(cond)); } - auto manifest = mb.build_manifest(); - ddwaf::rule rule("id", "name", "type", "category", std::move(conditions)); + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; + + ddwaf::rule rule("id", "name", std::move(tags), std::move(conditions)); // 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. @@ -336,7 +337,7 @@ TEST(TestRule, FullCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_TRUE(event.has_value()); } @@ -350,247 +351,26 @@ TEST(TestRule, FullCachedMatchSecondRun) store.insert(root); ddwaf::timer deadline{2s}; - auto event = rule.match(store, manifest, cache, {}, deadline); + auto event = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(event.has_value()); } } -TEST(TestRule, ToggleSingleRule) -{ - auto rule = readFile("toggle_rules.yaml"); - 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); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_MATCH); - - ddwaf_context_destroy(context); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-1", ddwaf_object_bool(&tmp, false)); - - EXPECT_EQ(ddwaf_toggle_rules(handle, &root), DDWAF_OK); - - ddwaf_object_free(&root); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - - EXPECT_EQ(ddwaf_run(context, &root, NULL, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - ddwaf_destroy(handle); -} - -TEST(TestRule, ToggleRuleInCollection) -{ - auto rule = readFile("toggle_rules.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - std::unordered_map events = { - {"id-rule-3", ddwaf::test::event{.id = "id-rule-3", - .name = "rule3", - .type = "flow2", - .category = "category3", - .matches = {{.op = "match_regex", - .op_value = "rule2", - .address = "value2", - .value = "rule2", - .highlight = "rule2"}}}}, - {"id-rule-2", ddwaf::test::event{.id = "id-rule-2", - .name = "rule2", - .type = "flow2", - .category = "category2", - .matches = {{.op = "match_regex", - .op_value = "rule2", - .address = "value2", - .value = "rule2", - .highlight = "rule2"}}}}}; - - // Due to the use of unordered structures we can't really know which rule - // will match first as it's implementation dependent, so we keep track of - // which one matched first. - std::string first_id; - std::string second_id; - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value2", ddwaf_object_string(&tmp, "rule2")); - - ddwaf_result res; - EXPECT_EQ(ddwaf_run(context, &root, &res, LONG_TIME), DDWAF_MATCH); - EXPECT_NE(res.data, nullptr); - - if (strstr(res.data, "id-rule-3") != nullptr) { - first_id = "id-rule-3"; - second_id = "id-rule-2"; - } else { - first_id = "id-rule-2"; - second_id = "id-rule-3"; - } - - EXPECT_EVENTS(res, events[first_id]); - - ddwaf_result_free(&res); - ddwaf_context_destroy(context); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, first_id.c_str(), ddwaf_object_bool(&tmp, false)); - - EXPECT_EQ(ddwaf_toggle_rules(handle, &root), DDWAF_OK); - - ddwaf_object_free(&root); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value2", ddwaf_object_string(&tmp, "rule2")); - - ddwaf_result res; - EXPECT_EQ(ddwaf_run(context, &root, &res, LONG_TIME), DDWAF_MATCH); - EXPECT_EVENTS(res, events[second_id]); - - ddwaf_result_free(&res); - ddwaf_context_destroy(context); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, second_id.c_str(), ddwaf_object_bool(&tmp, false)); - - EXPECT_EQ(ddwaf_toggle_rules(handle, &root), DDWAF_OK); - - ddwaf_object_free(&root); - } - - { - ddwaf_context context = ddwaf_context_init(handle); - ASSERT_NE(context, nullptr); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value2", ddwaf_object_string(&tmp, "rule2")); - - EXPECT_EQ(ddwaf_run(context, &root, nullptr, LONG_TIME), DDWAF_OK); - - ddwaf_context_destroy(context); - } - - ddwaf_destroy(handle); -} - -TEST(TestRule, ToggleNonExistentRules) -{ - auto rule = readFile("toggle_rules.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-4", ddwaf_object_bool(&tmp, false)); - - EXPECT_EQ(ddwaf_toggle_rules(handle, &root), DDWAF_OK); - - ddwaf_object_free(&root); - - ddwaf_destroy(handle); -} - -TEST(TestRule, ToggleWithInvalidObject) -{ - auto rule = readFile("toggle_rules.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - { - ddwaf_object root, tmp; - ddwaf_object_array(&root); - ddwaf_object_array_add(&root, ddwaf_object_bool(&tmp, false)); - - EXPECT_EQ(ddwaf_toggle_rules(handle, &root), DDWAF_ERR_INVALID_OBJECT); - - ddwaf_object_free(&root); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-1", ddwaf_object_unsigned(&tmp, 5)); - - EXPECT_EQ(ddwaf_toggle_rules(handle, &root), DDWAF_ERR_INVALID_OBJECT); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(ddwaf_toggle_rules(handle, nullptr), DDWAF_ERR_INVALID_ARGUMENT); - - ddwaf_destroy(handle); -} - -TEST(TestRule, ToggleWithInvalidHandle) -{ - EXPECT_EQ(ddwaf_toggle_rules(nullptr, nullptr), DDWAF_ERR_INVALID_ARGUMENT); -} - TEST(TestRule, ExcludeObject) { - std::vector targets; - - ddwaf::manifest_builder mb; - targets.push_back(mb.insert("http.client_ip", {})); + std::vector targets; - auto manifest = mb.build_manifest(); + ddwaf::manifest manifest; + targets.push_back({manifest.insert("http.client_ip"), "http.client_ip", {}}); auto cond = std::make_shared(std::move(targets), std::vector{}, std::make_unique(std::vector{"192.168.0.1"})); std::vector> conditions{std::move(cond)}; + std::unordered_map tags{{"type", "type"}, {"category", "category"}}; ddwaf::rule rule( - "id", "name", "type", "category", std::move(conditions), {"update", "block", "passlist"}); + "id", "name", std::move(tags), std::move(conditions), {"update", "block", "passlist"}); ddwaf_object root, tmp; ddwaf_object_map(&root); @@ -602,6 +382,6 @@ TEST(TestRule, ExcludeObject) ddwaf::timer deadline{2s}; rule::cache_type cache; - auto event = rule.match(store, manifest, cache, {&root.array[0]}, deadline); + auto event = rule.match(store, cache, {&root.array[0]}, {}, deadline); EXPECT_FALSE(event.has_value()); } diff --git a/tests/ruleset_test.cpp b/tests/ruleset_test.cpp index 8cadca877..39212d532 100644 --- a/tests/ruleset_test.cpp +++ b/tests/ruleset_test.cpp @@ -11,17 +11,29 @@ using namespace ddwaf; namespace { std::vector test_rules() { - return {std::make_shared("id0", "name", "type0", "category0", + return {std::make_shared("id0", "name", + std::unordered_map{ + {"type", "type0"}, {"category", "category0"}}, std::vector{}, std::vector{}), - std::make_shared("id1", "name", "type1", "category0", + std::make_shared("id1", "name", + std::unordered_map{ + {"type", "type1"}, {"category", "category0"}}, std::vector{}, std::vector{}), - std::make_shared("id2", "name", "type1", "category0", + std::make_shared("id2", "name", + std::unordered_map{ + {"type", "type1"}, {"category", "category0"}}, std::vector{}, std::vector{}), - std::make_shared("id3", "name", "type2", "category0", + std::make_shared("id3", "name", + std::unordered_map{ + {"type", "type2"}, {"category", "category0"}}, std::vector{}, std::vector{}), - std::make_shared("id4", "name", "type2", "category1", + std::make_shared("id4", "name", + std::unordered_map{ + {"type", "type2"}, {"category", "category1"}}, std::vector{}, std::vector{}), - std::make_shared("id5", "name", "type2", "category1", + std::make_shared("id5", "name", + std::unordered_map{ + {"type", "type2"}, {"category", "category1"}}, std::vector{}, std::vector{})}; } } // namespace @@ -33,110 +45,15 @@ TEST(TestRuleset, Insert) EXPECT_EQ(ruleset.rules.size(), 6); EXPECT_EQ(ruleset.collections.size(), 3); - EXPECT_EQ(ruleset.rules_by_type.size(), 3); - EXPECT_EQ(ruleset.rules_by_category.size(), 2); } -TEST(TestRuleset, ByCategory) +TEST(TestRuleset, InsertContainer) { - auto rules = test_rules(); ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } + std::unordered_map rules; + for (const auto &rule : test_rules()) { rules.emplace(rule->id, rule); } + ruleset.insert_rules(rules); - { - auto by_category = ruleset.get_rules_by_category("category0"); - EXPECT_EQ(by_category.size(), 4); - EXPECT_NE(by_category.find(rules[0]), by_category.end()); - EXPECT_NE(by_category.find(rules[1]), by_category.end()); - EXPECT_NE(by_category.find(rules[2]), by_category.end()); - EXPECT_NE(by_category.find(rules[3]), by_category.end()); - } - - { - auto by_category = ruleset.get_rules_by_category("category1"); - EXPECT_EQ(by_category.size(), 2); - EXPECT_NE(by_category.find(rules[4]), by_category.end()); - EXPECT_NE(by_category.find(rules[5]), by_category.end()); - } - - { - auto by_category = ruleset.get_rules_by_category("category2"); - EXPECT_EQ(by_category.size(), 0); - } -} - -TEST(TestRuleset, ByType) -{ - auto rules = test_rules(); - ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } - - { - auto by_type = ruleset.get_rules_by_type("type0"); - EXPECT_EQ(by_type.size(), 1); - EXPECT_NE(by_type.find(rules[0]), by_type.end()); - } - - { - auto by_type = ruleset.get_rules_by_type("type1"); - EXPECT_EQ(by_type.size(), 2); - EXPECT_NE(by_type.find(rules[1]), by_type.end()); - EXPECT_NE(by_type.find(rules[2]), by_type.end()); - } - - { - auto by_type = ruleset.get_rules_by_type("type2"); - EXPECT_EQ(by_type.size(), 3); - EXPECT_NE(by_type.find(rules[3]), by_type.end()); - EXPECT_NE(by_type.find(rules[4]), by_type.end()); - EXPECT_NE(by_type.find(rules[5]), by_type.end()); - } - - { - auto by_type = ruleset.get_rules_by_type("type3"); - EXPECT_EQ(by_type.size(), 0); - } -} - -TEST(TestRuleset, ByTypeAndCategory) -{ - auto rules = test_rules(); - ddwaf::ruleset ruleset; - for (const auto &rule : rules) { ruleset.insert_rule(rule); } - - { - auto by_tags = ruleset.get_rules_by_type_and_category("type0", "category0"); - EXPECT_EQ(by_tags.size(), 1); - EXPECT_NE(by_tags.find(rules[0]), by_tags.end()); - } - - { - auto by_tags = ruleset.get_rules_by_type_and_category("type1", "category0"); - EXPECT_EQ(by_tags.size(), 2); - EXPECT_NE(by_tags.find(rules[1]), by_tags.end()); - EXPECT_NE(by_tags.find(rules[2]), by_tags.end()); - } - - { - auto by_tags = ruleset.get_rules_by_type_and_category("type2", "category0"); - EXPECT_EQ(by_tags.size(), 1); - EXPECT_NE(by_tags.find(rules[3]), by_tags.end()); - } - - { - auto by_tags = ruleset.get_rules_by_type_and_category("type0", "category1"); - EXPECT_EQ(by_tags.size(), 0); - } - - { - auto by_tags = ruleset.get_rules_by_type_and_category("type1", "category1"); - EXPECT_EQ(by_tags.size(), 0); - } - - { - auto by_tags = ruleset.get_rules_by_type_and_category("type2", "category1"); - EXPECT_EQ(by_tags.size(), 2); - EXPECT_NE(by_tags.find(rules[4]), by_tags.end()); - EXPECT_NE(by_tags.find(rules[5]), by_tags.end()); - } + EXPECT_EQ(ruleset.rules.size(), 6); + EXPECT_EQ(ruleset.collections.size(), 3); } diff --git a/tests/test.h b/tests/test.h index 574920d67..f8e6be22d 100644 --- a/tests/test.h +++ b/tests/test.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -38,11 +39,13 @@ using namespace std; #include #include #include +#include #include #include #include #include -#include +#include +#include #include #include #include @@ -88,3 +91,8 @@ using namespace ddwaf; { \ NULL, 0, {string}, length, DDWAF_OBJ_STRING \ } + +#define EXPECT_STR(a, b) EXPECT_STREQ(a.c_str(), b) +#define EXPECT_STRV(a, b) EXPECT_STREQ(a.data(), b) + + diff --git a/tests/waf_test.cpp b/tests/waf_test.cpp index 0bafd8076..a01f76c4f 100644 --- a/tests/waf_test.cpp +++ b/tests/waf_test.cpp @@ -14,180 +14,52 @@ TEST(TestWaf, RootAddresses) ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); + ddwaf::waf instance{ + rule, info, ddwaf::object_limits(), ddwaf_object_free, std::make_shared()}; ddwaf_object_free(&rule); std::set available_addresses{"value1", "value2"}; - for (auto address : instance->get_root_addresses()) { + for (const auto *address : instance.get_root_addresses()) { EXPECT_NE(available_addresses.find(address), available_addresses.end()); } } -TEST(TestWaf, RuleDatIDs) -{ - auto rule = readFile("rule_data.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); - ddwaf_object_free(&rule); - - std::set available_ids{"usr_data", "ip_data"}; - for (auto id : instance->get_rule_data_ids()) { - EXPECT_NE(available_ids.find(id), available_ids.end()); - } -} - -TEST(TestWaf, EmptyRuleDatIDs) -{ - auto rule = readFile("interface.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); - ddwaf_object_free(&rule); - - EXPECT_TRUE(instance->get_rule_data_ids().empty()); -} - TEST(TestWaf, BasicContextRun) { auto rule = readFile("interface.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); + ddwaf::waf instance{ + rule, info, ddwaf::object_limits(), ddwaf_object_free, std::make_shared()}; ddwaf_object_free(&rule); - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - auto ctx = instance->create_context(); + auto ctx = instance.create_context(); EXPECT_EQ(ctx.run(root, std::nullopt, LONG_TIME), DDWAF_MATCH); } -TEST(TestWaf, ToggleRule) -{ - auto rule = readFile("toggle_rules.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); - ddwaf_object_free(&rule); - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - - auto ctx = instance->create_context(); - EXPECT_EQ(ctx.run(root, std::nullopt, LONG_TIME), DDWAF_MATCH); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-1", ddwaf_object_bool(&tmp, false)); - - EXPECT_NO_THROW(instance->toggle_rules(parameter(root))); - - ddwaf_object_free(&root); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - - auto ctx = instance->create_context(); - EXPECT_EQ(ctx.run(root, std::nullopt, LONG_TIME), DDWAF_OK); - } -} - -TEST(TestWaf, ToggleNonExistentRules) -{ - auto rule = readFile("toggle_rules.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); - ddwaf_object_free(&rule); - - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-4", ddwaf_object_bool(&tmp, false)); - - EXPECT_NO_THROW(instance->toggle_rules(parameter(root))); - - ddwaf_object_free(&root); -} - -TEST(TestWaf, ToggleWithInvalidObject) -{ - auto rule = readFile("toggle_rules.yaml"); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); - ASSERT_NE(instance.get(), nullptr); - ddwaf_object_free(&rule); - - { - ddwaf_object root, tmp; - ddwaf_object_array(&root); - ddwaf_object_array_add(&root, ddwaf_object_bool(&tmp, false)); - - EXPECT_THROW(instance->toggle_rules(parameter(root)), ddwaf::bad_cast); - - ddwaf_object_free(&root); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-1", ddwaf_object_unsigned(&tmp, 5)); - - EXPECT_THROW(instance->toggle_rules(parameter(root)), ddwaf::bad_cast); - - ddwaf_object_free(&root); - } -} - TEST(TestWaf, RuleDisabledInRuleset) { auto rule = readFile("rule_disabled.yaml"); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); ddwaf::ruleset_info info; - auto instance = waf::from_config(rule, nullptr, info); + ddwaf::waf instance{ + rule, info, ddwaf::object_limits(), ddwaf_object_free, std::make_shared()}; ddwaf_object_free(&rule); { - ddwaf_object root, tmp; + ddwaf_object root; + ddwaf_object tmp; ddwaf_object_map(&root); ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - auto ctx = instance->create_context(); + auto ctx = instance.create_context(); EXPECT_EQ(ctx.run(root, std::nullopt, LONG_TIME), DDWAF_OK); } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "id-rule-1", ddwaf_object_bool(&tmp, true)); - - EXPECT_NO_THROW(instance->toggle_rules(parameter(root))); - - ddwaf_object_free(&root); - } - - { - ddwaf_object root, tmp; - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); - - auto ctx = instance->create_context(); - EXPECT_EQ(ctx.run(root, std::nullopt, LONG_TIME), DDWAF_MATCH); - } } diff --git a/tests/yaml/interface.yaml b/tests/yaml/interface.yaml index e0f0f002a..3f924343a 100644 --- a/tests/yaml/interface.yaml +++ b/tests/yaml/interface.yaml @@ -5,6 +5,7 @@ rules: tags: type: flow1 category: category1 + confidence: 1 conditions: - operator: match_regex parameters: @@ -28,6 +29,7 @@ rules: tags: type: flow2 category: category3 + confidence: 1 conditions: - operator: match_regex parameters: diff --git a/tests/yaml/interface3.yaml b/tests/yaml/interface3.yaml new file mode 100644 index 000000000..b92f9eb02 --- /dev/null +++ b/tests/yaml/interface3.yaml @@ -0,0 +1,14 @@ +version: '2.1' +rules: + - id: 1 + name: rule1 + tags: + type: flow1 + category: category1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + - address: value2 + regex: rule1 diff --git a/tests/yaml/interface_with_data.yaml b/tests/yaml/interface_with_data.yaml new file mode 100644 index 000000000..0e6e3ab96 --- /dev/null +++ b/tests/yaml/interface_with_data.yaml @@ -0,0 +1,50 @@ +version: '2.1' +rules: + - id: 1 + name: rule1 + tags: + type: flow1 + category: category1 + confidence: 1 + conditions: + - operator: ip_match + parameters: + inputs: + - address: http.client_ip + data: ip_data + - id: 3 + name: rule3 + tags: + type: flow3 + category: category3 + confidence: 1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: server.request.query + regex: rule3 + - id: 4 + name: rule4 + tags: + type: flow4 + category: category4 + conditions: + - operator: match_regex + parameters: + inputs: + - address: server.request.params + regex: rule4 + - id: 5 + name: rule5 + tags: + type: flow5 + category: category5 + confidence: 1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: server.response.status + regex: rule5 + diff --git a/tests/yaml/rule_data.yaml b/tests/yaml/rule_data.yaml index 8a984dded..8a424ef0a 100644 --- a/tests/yaml/rule_data.yaml +++ b/tests/yaml/rule_data.yaml @@ -5,6 +5,7 @@ rules: tags: type: flow1 category: category1 + confidence: 1 conditions: - operator: ip_match parameters: @@ -16,6 +17,7 @@ rules: tags: type: flow2 category: category2 + confidence: 0 conditions: - operator: exact_match parameters: diff --git a/third_party/libinjection/src/libinjection_xss.c b/third_party/libinjection/src/libinjection_xss.c index ba343f3f2..3bffd5c66 100644 --- a/third_party/libinjection/src/libinjection_xss.c +++ b/third_party/libinjection/src/libinjection_xss.c @@ -729,7 +729,6 @@ static attribute_t is_black_attr(const char* s, size_t len) #if 0 // 2017-10-09 - commented out to prevent some false positives: - // https://admin-infra.sqreen.io/test_attacks/59de90aa36fe25000a02b2fe /* XMLNS can be used to create arbitrary tags */ if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) { DEBUG_PRINT("Got XMLNS and XLINK tags\n"); @@ -928,7 +927,6 @@ int libinjection_is_xss(const char* s, size_t len, int flags) DEBUG_PRINT("BACK TICK\n"); #if 0 // 2017-10-09 - commented out to prevent some false positives: - // https://admin-infra.sqreen.io/test_attacks/59de941d36698c00079902f1 return 1; #endif } diff --git a/validator/ruleset.yaml b/validator/ruleset.yaml index d6a743600..deb09f31a 100644 --- a/validator/ruleset.yaml +++ b/validator/ruleset.yaml @@ -654,3 +654,20 @@ rules: - address: rule40-input1 - address: rule40-input2 regex: rule40 + - id: 41 + name: rule41-ip-match-dynamic + tags: + type: flow41 + category: category + conditions: + - operator: ip_match + parameters: + inputs: + - address: rule41-input + data: rule_41_data +rules_data: + - id: rule_41_data + type: ip_with_expiration + data: + - value: 192.168.1.1 + expiration: 0 diff --git a/version b/version index 9dbb0c005..afa2b3515 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.7.0 \ No newline at end of file +1.8.0 \ No newline at end of file