diff --git a/.clang-tidy b/.clang-tidy index 7216dcae0..b0153e4fd 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,7 @@ --- # readability-function-cognitive-complexity temporarily disabled until clang-tidy is fixed # right now emalloc causes it to misbehave -Checks: '*,misc-const-correctness,-bugprone-reserved-identifier,-hicpp-signed-bitwise,-llvmlibc-restrict-system-libc-headers,-altera-unroll-loops,-hicpp-named-parameter,-cert-dcl37-c,-cert-dcl51-cpp,-read,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-non-const-global-variables,-altera-id-dependent-backward-branch,-performance-no-int-to-ptr,-altera-struct-pack-align,-google-readability-casting,-modernize-use-trailing-return-type,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-cppcoreguidelines-pro-type-union-access,-fuchsia-default-arguments-calls,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-google-readability-todo,-llvm-header-guard,-readability-function-cognitive-complexity,-readability-identifier-length,-cppcoreguidelines-owning-memory,-cert-err58-cpp,-fuchsia-statically-constructed-objects,-google-build-using-namespace,-hicpp-avoid-goto,-cppcoreguidelines-avoid-goto,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-abseil-string-find-str-contains,-bugprone-unchecked-optional-access,-readability-use-anyofallof,-modernize-loop-convert,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-cppcoreguidelines-no-malloc,-llvmlibc-inline-function-decl' +Checks: '*,misc-const-correctness,-bugprone-reserved-identifier,-hicpp-signed-bitwise,-llvmlibc-restrict-system-libc-headers,-altera-unroll-loops,-hicpp-named-parameter,-cert-dcl37-c,-cert-dcl51-cpp,-read,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-non-const-global-variables,-altera-id-dependent-backward-branch,-performance-no-int-to-ptr,-altera-struct-pack-align,-google-readability-casting,-modernize-use-trailing-return-type,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-cppcoreguidelines-pro-type-union-access,-fuchsia-default-arguments-calls,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-google-readability-todo,-llvm-header-guard,-readability-function-cognitive-complexity,-readability-identifier-length,-cppcoreguidelines-owning-memory,-cert-err58-cpp,-fuchsia-statically-constructed-objects,-google-build-using-namespace,-hicpp-avoid-goto,-cppcoreguidelines-avoid-goto,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-abseil-string-find-str-contains,-bugprone-unchecked-optional-access,-readability-use-anyofallof,-modernize-loop-convert,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-cppcoreguidelines-no-malloc,-llvmlibc-inline-function-decl,-boost-use-ranges,-modernize-use-ranges' WarningsAsErrors: '*' HeaderFilterRegex: '' CheckOptions: diff --git a/cmake/objects.cmake b/cmake/objects.cmake index 7916ed66a..8c23aef38 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -1,5 +1,4 @@ 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/interface.cpp @@ -17,13 +16,15 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/obfuscator.cpp ${libddwaf_SOURCE_DIR}/src/uri_utils.cpp ${libddwaf_SOURCE_DIR}/src/utils.cpp - ${libddwaf_SOURCE_DIR}/src/waf.cpp ${libddwaf_SOURCE_DIR}/src/platform.cpp ${libddwaf_SOURCE_DIR}/src/sha256.cpp ${libddwaf_SOURCE_DIR}/src/uuid.cpp ${libddwaf_SOURCE_DIR}/src/action_mapper.cpp + ${libddwaf_SOURCE_DIR}/src/builder/action_mapper_builder.cpp + ${libddwaf_SOURCE_DIR}/src/builder/matcher_builder.cpp ${libddwaf_SOURCE_DIR}/src/builder/module_builder.cpp ${libddwaf_SOURCE_DIR}/src/builder/processor_builder.cpp + ${libddwaf_SOURCE_DIR}/src/builder/ruleset_builder.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/sql_base.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/pgsql.cpp ${libddwaf_SOURCE_DIR}/src/tokenizer/mysql.cpp @@ -33,19 +34,19 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/exclusion/input_filter.cpp ${libddwaf_SOURCE_DIR}/src/exclusion/object_filter.cpp ${libddwaf_SOURCE_DIR}/src/exclusion/rule_filter.cpp - ${libddwaf_SOURCE_DIR}/src/parser/actions_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/common.cpp - ${libddwaf_SOURCE_DIR}/src/parser/parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/parser_v1.cpp - ${libddwaf_SOURCE_DIR}/src/parser/data_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/processor_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/expression_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/matcher_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/transformer_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/rule_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/rule_override_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/scanner_parser.cpp - ${libddwaf_SOURCE_DIR}/src/parser/exclusion_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/common/expression_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/common/matcher_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/common/transformer_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/common/reference_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/actions_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/data_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/exclusion_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/processor_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/rule_override_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/rule_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/legacy_rule_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/scanner_parser.cpp + ${libddwaf_SOURCE_DIR}/src/configuration/configuration_manager.cpp ${libddwaf_SOURCE_DIR}/src/processor/extract_schema.cpp ${libddwaf_SOURCE_DIR}/src/processor/fingerprint.cpp ${libddwaf_SOURCE_DIR}/src/condition/exists.cpp diff --git a/include/ddwaf.h b/include/ddwaf.h index b71c72a7b..4ec75fec1 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -11,17 +11,19 @@ namespace ddwaf{ class waf; class context_wrapper; +class waf_builder; } // namespace ddwaf using ddwaf_handle = ddwaf::waf *; using ddwaf_context = ddwaf::context_wrapper *; +using ddwaf_builder = ddwaf::waf_builder *; extern "C" { #endif #include -#include #include +#include #define DDWAF_MAX_STRING_LENGTH 4096 #define DDWAF_MAX_CONTAINER_DEPTH 20 @@ -61,11 +63,11 @@ typedef enum **/ typedef enum { - DDWAF_ERR_INTERNAL = -3, - DDWAF_ERR_INVALID_OBJECT = -2, + DDWAF_ERR_INTERNAL = -3, + DDWAF_ERR_INVALID_OBJECT = -2, DDWAF_ERR_INVALID_ARGUMENT = -1, - DDWAF_OK = 0, - DDWAF_MATCH = 1, + DDWAF_OK = 0, + DDWAF_MATCH = 1, } DDWAF_RET_CODE; /** @@ -86,6 +88,7 @@ typedef enum #ifndef __cplusplus typedef struct _ddwaf_handle* ddwaf_handle; typedef struct _ddwaf_context* ddwaf_context; +typedef struct _ddwaf_builder* ddwaf_builder; #endif typedef struct _ddwaf_object ddwaf_object; @@ -207,22 +210,6 @@ typedef void (*ddwaf_log_cb)( ddwaf_handle ddwaf_init(const ddwaf_object *ruleset, const ddwaf_config* config, ddwaf_object *diagnostics); -/** - * ddwaf_update - * - * Update a ddwaf instance - * - * @param ruleset ddwaf::object map containing rules, exclusions, rules_override and rules_data. (nonnull) - * @param diagnostics Optional ruleset parsing diagnostics. (nullable) - * - * @return Handle to the new WAF instance or NULL if there was an error processing the ruleset. - * - * @note If handle or ruleset are NULL, the diagnostics object will not be initialised. - * @note This function is not thread-safe - **/ -ddwaf_handle ddwaf_update(ddwaf_handle handle, const ddwaf_object *ruleset, - ddwaf_object *diagnostics); - /** * ddwaf_destroy * @@ -364,6 +351,69 @@ void ddwaf_context_destroy(ddwaf_context context); **/ void ddwaf_result_free(ddwaf_result *result); +/** + * ddwaf_builder_init + * + * Initialize an instace of the waf builder. + * + * @param config Optional configuration of the WAF. (nullable) + * + * @return Handle to the builer instance or NULL on error. + * + * @note If config is NULL, default values will be used, including the default + * free function (ddwaf_object_free). + **/ +ddwaf_builder ddwaf_builder_init(const ddwaf_config *config); + +/** + * ddwaf_builder_add_or_update_config + * + * Adds or updates a configuration based on the given path, which must be a unique + * identifier for the provided configuration. + * + * @param builder Builder to perform the operation on. (nonnull) + * @param path A string containing the path of the configuration, this must uniquely identify the configuration. (nonnull) + * @param path_len The length of the string contained within path. + * @param config ddwaf::object map containing rules, exclusions, rules_override and rules_data. (nonnull) + * @param diagnostics Optional ruleset parsing diagnostics. (nullable) + * + * @return Whether the operation succeeded (true) or failed (false). + **/ +bool ddwaf_builder_add_or_update_config(ddwaf_builder builder, const char *path, uint32_t path_len, ddwaf_object *config, ddwaf_object *diagnostics); + +/** + * ddwaf_builder_remove_config + * + * Removes a configuration based on the provided path. + * + * @param builder Builder to perform the operation on. (nonnull) + * @param path A string containing the path of the configuration to be removed. (nonnull) + * @param path_len The length of the string contained within path. + * + * @return Whether the operation succeeded (true) or failed (false). + **/ +bool ddwaf_builder_remove_config(ddwaf_builder builder, const char *path, uint32_t path_len); + +/** + * ddwaf_builder_build_instance + * + * Builds a ddwaf instance based on the current set of configurations. + * + * @param builder Builder to perform the operation on. (nonnull) + * + * @return Handle to the new WAF instance or NULL if there was an error. + **/ +ddwaf_handle ddwaf_builder_build_instance(ddwaf_builder builder); + +/** + * ddwaf_builder_destroy + * + * Destroy an instance of the builder. + * + * @param builder Builder to perform the operation on. (nonnull) + */ +void ddwaf_builder_destroy(ddwaf_builder builder); + /** * ddwaf_object_invalid * diff --git a/libddwaf.def b/libddwaf.def index 4e8a4563b..7a66580ba 100644 --- a/libddwaf.def +++ b/libddwaf.def @@ -1,8 +1,12 @@ LIBRARY ddwaf EXPORTS ddwaf_init - ddwaf_update ddwaf_destroy + ddwaf_builder_init + ddwaf_builder_add_or_update_config + ddwaf_builder_remove_config + ddwaf_builder_build_instance + ddwaf_builder_destroy ddwaf_known_addresses ddwaf_context_init ddwaf_run diff --git a/src/action_mapper.cpp b/src/action_mapper.cpp index 7be195c19..4eb1ae204 100644 --- a/src/action_mapper.cpp +++ b/src/action_mapper.cpp @@ -4,14 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include -#include -#include -#include -#include #include -#include -#include #include "action_mapper.hpp" @@ -37,55 +30,4 @@ action_type action_type_from_string(std::string_view type) return action_type::unknown; } -void action_mapper_builder::alias_default_action_to(std::string_view default_id, std::string alias) -{ - auto it = default_actions_.find(default_id); - if (it == default_actions_.end()) { - throw std::runtime_error( - "attempting to add alias to non-existent default action " + std::string(default_id)); - } - action_by_id_.emplace(std::move(alias), it->second); -} - -void action_mapper_builder::set_action( - std::string id, std::string type, std::unordered_map parameters) -{ - if (action_by_id_.find(id) != action_by_id_.end()) { - throw std::runtime_error("duplicate action '" + id + '\''); - } - - action_by_id_.emplace(std::move(id), - action_spec{action_type_from_string(type), std::move(type), std::move(parameters)}); -} - -[[nodiscard]] const action_spec &action_mapper_builder::get_default_action(std::string_view id) -{ - auto it = default_actions_.find(id); - if (it == default_actions_.end()) { - throw std::out_of_range("unknown action " + std::string(id)); - } - return it->second; -} - -std::shared_ptr action_mapper_builder::build_shared() -{ - return std::make_shared(build()); -} - -action_mapper action_mapper_builder::build() -{ - for (const auto &[action_id, action_spec] : default_actions_) { - action_by_id_.try_emplace(action_id, action_spec); - } - - return std::move(action_by_id_); -} - -const std::map> action_mapper_builder::default_actions_ = { - {"block", {action_type::block_request, "block_request", - {{"status_code", "403"}, {"type", "auto"}, {"grpc_status_code", "10"}}}}, - {"stack_trace", {action_type::generate_stack, "generate_stack", {}}}, - {"extract_schema", {action_type::generate_schema, "generate_schema", {}}}, - {"monitor", {action_type::monitor, "monitor", {}}}}; - } // namespace ddwaf diff --git a/src/action_mapper.hpp b/src/action_mapper.hpp index 14275dee5..63b8509ee 100644 --- a/src/action_mapper.hpp +++ b/src/action_mapper.hpp @@ -12,8 +12,6 @@ #include #include -#include "utils.hpp" - namespace ddwaf { enum class action_type : uint8_t { @@ -34,38 +32,12 @@ inline bool is_blocking_action(action_type type) return type == action_type::block_request || type == action_type::redirect_request; } -struct action_spec { +struct action_parameters { action_type type; std::string type_str; std::unordered_map parameters; }; -using action_mapper = std::map>; - -class action_mapper_builder { -public: - action_mapper_builder() = default; - ~action_mapper_builder() = default; - action_mapper_builder(const action_mapper_builder &) = delete; - action_mapper_builder(action_mapper_builder &&) = delete; - action_mapper_builder &operator=(const action_mapper_builder &) = delete; - action_mapper_builder &operator=(action_mapper_builder &&) = delete; - - void alias_default_action_to(std::string_view default_id, std::string alias); - - void set_action( - std::string id, std::string type, std::unordered_map parameters); - - [[nodiscard]] static const action_spec &get_default_action(std::string_view id); - - std::shared_ptr build_shared(); - - // Used for testing - action_mapper build(); - -protected: - std::map> action_by_id_; - static const std::map> default_actions_; -}; +using action_mapper = std::map>; } // namespace ddwaf diff --git a/src/builder/action_mapper_builder.cpp b/src/builder/action_mapper_builder.cpp new file mode 100644 index 000000000..c33098bf3 --- /dev/null +++ b/src/builder/action_mapper_builder.cpp @@ -0,0 +1,70 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "action_mapper.hpp" +#include "builder/action_mapper_builder.hpp" + +namespace ddwaf { + +void action_mapper_builder::set_action(const std::string &id, std::string type, + std::unordered_map parameters) +{ + auto [it, res] = + action_by_id_.try_emplace(id, action_parameters{.type = action_type_from_string(type), + .type_str = std::move(type), + .parameters = std::move(parameters)}); + if (!res) { + throw std::runtime_error("duplicate action '" + id + '\''); + } +} + +[[nodiscard]] const action_parameters &action_mapper_builder::get_default_action( + std::string_view id) +{ + auto it = default_actions_.find(id); + if (it == default_actions_.end()) { + throw std::out_of_range("unknown action " + std::string(id)); + } + return it->second; +} + +std::shared_ptr action_mapper_builder::build_shared() +{ + return std::make_shared(build()); +} + +action_mapper action_mapper_builder::build() +{ + for (const auto &[action_id, action_parameters] : default_actions_) { + action_by_id_.try_emplace(action_id, action_parameters); + } + + return std::move(action_by_id_); +} + +const std::map> + action_mapper_builder::default_actions_ = { + {"block", {.type = action_type::block_request, + .type_str = "block_request", + .parameters = {{"status_code", "403"}, {"type", "auto"}, + {"grpc_status_code", "10"}}}}, + {"stack_trace", + {.type = action_type::generate_stack, .type_str = "generate_stack", .parameters = {}}}, + {"extract_schema", {.type = action_type::generate_schema, + .type_str = "generate_schema", + .parameters = {}}}, + {"monitor", {.type = action_type::monitor, .type_str = "monitor", .parameters = {}}}}; + +} // namespace ddwaf diff --git a/src/builder/action_mapper_builder.hpp b/src/builder/action_mapper_builder.hpp new file mode 100644 index 000000000..b3d7f62e8 --- /dev/null +++ b/src/builder/action_mapper_builder.hpp @@ -0,0 +1,43 @@ +// 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 "action_mapper.hpp" + +namespace ddwaf { + +class action_mapper_builder { +public: + action_mapper_builder() = default; + ~action_mapper_builder() = default; + action_mapper_builder(const action_mapper_builder &) = delete; + action_mapper_builder(action_mapper_builder &&) = delete; + action_mapper_builder &operator=(const action_mapper_builder &) = delete; + action_mapper_builder &operator=(action_mapper_builder &&) = delete; + + void set_action(const std::string &id, std::string type, + std::unordered_map parameters); + + [[nodiscard]] static const action_parameters &get_default_action(std::string_view id); + + std::shared_ptr build_shared(); + + // Used for testing + action_mapper build(); + +protected: + std::map> action_by_id_; + static const std::map> default_actions_; +}; + +} // namespace ddwaf diff --git a/src/builder/matcher_builder.cpp b/src/builder/matcher_builder.cpp new file mode 100644 index 000000000..3c91639f2 --- /dev/null +++ b/src/builder/matcher_builder.cpp @@ -0,0 +1,28 @@ +// 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 "builder/matcher_builder.hpp" +#include "configuration/common/configuration.hpp" +#include "matcher/base.hpp" +#include "matcher/exact_match.hpp" +#include "matcher/ip_match.hpp" +#include + +namespace ddwaf { + +std::shared_ptr matcher_builder::build(const data_spec &data) +{ + std::shared_ptr matcher; + if (data.type == data_type::ip_with_expiration) { + matcher = std::make_shared(data.values); + } else if (data.type == data_type::data_with_expiration) { + matcher = std::make_shared(data.values); + } + + return matcher; +} + +} // namespace ddwaf diff --git a/src/builder/matcher_builder.hpp b/src/builder/matcher_builder.hpp new file mode 100644 index 000000000..c3a836e5a --- /dev/null +++ b/src/builder/matcher_builder.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 "configuration/common/configuration.hpp" + +namespace ddwaf { + +struct matcher_builder { + static std::shared_ptr build(const data_spec &data); +}; + +} // namespace ddwaf diff --git a/src/builder/processor_builder.cpp b/src/builder/processor_builder.cpp index c5f61a026..82df50a3e 100644 --- a/src/builder/processor_builder.cpp +++ b/src/builder/processor_builder.cpp @@ -6,11 +6,12 @@ #include #include +#include #include #include "builder/processor_builder.hpp" +#include "configuration/common/configuration.hpp" #include "indexer.hpp" -#include "parser/specification.hpp" #include "processor/base.hpp" #include "processor/extract_schema.hpp" #include "processor/fingerprint.hpp" @@ -20,17 +21,17 @@ namespace ddwaf { namespace { std::set references_to_scanners( - const std::vector &references, const indexer &scanners) + const std::vector &references, const indexer &scanners) { std::set scanner_refs; for (const auto &ref : references) { - if (ref.type == parser::reference_type::id) { + if (ref.type == reference_type::id) { const auto *scanner = scanners.find_by_id(ref.ref_id); if (scanner == nullptr) { continue; } scanner_refs.emplace(scanner); - } else if (ref.type == parser::reference_type::tags) { + } else if (ref.type == reference_type::tags) { auto current_refs = scanners.find_by_tags(ref.tags); scanner_refs.merge(current_refs); } @@ -41,82 +42,83 @@ std::set references_to_scanners( template struct typed_processor_builder; template <> struct typed_processor_builder { - std::shared_ptr build(const auto &spec, const auto &scanners) + std::shared_ptr build(const auto &id, const auto &spec, const auto &scanners) { auto ref_scanners = references_to_scanners(spec.scanners, scanners); return std::make_shared( - spec.id, spec.expr, spec.mappings, std::move(ref_scanners), spec.evaluate, spec.output); + id, spec.expr, spec.mappings, std::move(ref_scanners), spec.evaluate, spec.output); } }; template <> struct typed_processor_builder { - std::shared_ptr build(const auto &spec) + std::shared_ptr build(const auto &id, const auto &spec) { return std::make_shared( - spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + id, spec.expr, spec.mappings, spec.evaluate, spec.output); } }; template <> struct typed_processor_builder { - std::shared_ptr build(const auto &spec) + std::shared_ptr build(const auto &id, const auto &spec) { return std::make_shared( - spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + id, spec.expr, spec.mappings, spec.evaluate, spec.output); } }; template <> struct typed_processor_builder { - std::shared_ptr build(const auto &spec) + std::shared_ptr build(const auto &id, const auto &spec) { return std::make_shared( - spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + id, spec.expr, spec.mappings, spec.evaluate, spec.output); } }; template <> struct typed_processor_builder { - std::shared_ptr build(const auto &spec) + std::shared_ptr build(const auto &id, const auto &spec) { return std::make_shared( - spec.id, spec.expr, spec.mappings, spec.evaluate, spec.output); + id, spec.expr, spec.mappings, spec.evaluate, spec.output); } }; -template +template concept has_build_with_scanners = - requires(typed_processor_builder b, Spec spec, Scanners scanners) { + requires(typed_processor_builder b, Id id, Spec spec, Scanners scanners) { { - b.build(spec, scanners) + b.build(id, spec, scanners) } -> std::same_as>; }; template [[nodiscard]] std::shared_ptr build_with_type( - const auto &spec, const auto &scanners) + const auto &id, const auto &spec, const auto &scanners) requires std::is_base_of_v { typed_processor_builder typed_builder; - if constexpr (has_build_with_scanners) { - return typed_builder.build(spec, scanners); + if constexpr (has_build_with_scanners) { + return typed_builder.build(id, spec, scanners); } else { - return typed_builder.build(spec); + return typed_builder.build(id, spec); } } } // namespace -[[nodiscard]] std::shared_ptr processor_builder::build( - const indexer &scanners) const +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::shared_ptr processor_builder::build( + const std::string &id, const processor_spec &spec, const indexer &scanners) { - switch (type) { + switch (spec.type) { case processor_type::extract_schema: - return build_with_type(*this, scanners); + return build_with_type(id, spec, scanners); case processor_type::http_endpoint_fingerprint: - return build_with_type(*this, scanners); + return build_with_type(id, spec, scanners); case processor_type::http_header_fingerprint: - return build_with_type(*this, scanners); + return build_with_type(id, spec, scanners); case processor_type::http_network_fingerprint: - return build_with_type(*this, scanners); + return build_with_type(id, spec, scanners); case processor_type::session_fingerprint: - return build_with_type(*this, scanners); + return build_with_type(id, spec, scanners); default: break; } diff --git a/src/builder/processor_builder.hpp b/src/builder/processor_builder.hpp index 0ab3be0f0..ee7809364 100644 --- a/src/builder/processor_builder.hpp +++ b/src/builder/processor_builder.hpp @@ -7,48 +7,16 @@ #pragma once #include -#include -#include +#include "configuration/common/configuration.hpp" #include "indexer.hpp" -#include "parser/specification.hpp" #include "processor/base.hpp" namespace ddwaf { -enum class processor_type : unsigned { - extract_schema, - // Reserved - http_endpoint_fingerprint, - http_network_fingerprint, - http_header_fingerprint, - session_fingerprint, -}; - struct processor_builder { - [[nodiscard]] std::shared_ptr build( - const indexer &scanners) const; - - processor_type type; - std::string id; - std::shared_ptr expr; - std::vector mappings; - std::vector scanners; - bool evaluate{false}; - bool output{true}; -}; - -struct processor_container { - [[nodiscard]] bool empty() const { return pre.empty() && post.empty(); } - [[nodiscard]] std::size_t size() const { return pre.size() + post.size(); } - void clear() - { - pre.clear(); - post.clear(); - } - - std::vector pre; - std::vector post; + static std::shared_ptr build( + const std::string &id, const processor_spec &spec, const indexer &scanners); }; } // namespace ddwaf diff --git a/src/builder/rule_builder.hpp b/src/builder/rule_builder.hpp new file mode 100644 index 000000000..b153a091a --- /dev/null +++ b/src/builder/rule_builder.hpp @@ -0,0 +1,79 @@ +// 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 "configuration/common/configuration.hpp" +#include "rule.hpp" + +namespace ddwaf { + +class rule_builder { +public: + explicit rule_builder(std::string id, rule_spec spec) + : id_(std::move(id)), spec_(std::move(spec)) + {} + + std::string_view get_id() const { return id_; } + const std::unordered_map &get_tags() const { return spec_.tags; } + [[nodiscard]] bool is_enabled() const { return spec_.enabled; } + + bool apply_override(const override_spec &ovrd) + { + if (ovrd.enabled.has_value()) { + spec_.enabled = *ovrd.enabled; + } + + if (ovrd.actions.has_value()) { + spec_.actions = *ovrd.actions; + } + + for (const auto &[key, value] : ovrd.tags) { + if (!spec_.tags.contains(key)) { + ancillary_tags_[key] = value; + } + } + + return true; + } + + // The builder should be considered invalid after calling build, as the memory + // associated with the rule_spec and overrides is transferred to the generated rule. + std::shared_ptr build(const action_mapper &mapper) + { + core_rule::verdict_type verdict = core_rule::verdict_type::monitor; + for (const auto &action : spec_.actions) { + auto it = mapper.find(action); + if (it == mapper.end()) { + continue; + } + + auto action_mode = it->second.type; + if (is_blocking_action(action_mode)) { + verdict = core_rule::verdict_type::block; + break; + } + } + + ancillary_tags_.merge(spec_.tags); + + return std::make_shared(std::move(id_), std::move(spec_.name), + std::move(ancillary_tags_), std::move(spec_.expr), std::move(spec_.actions), + spec_.enabled, spec_.source, verdict); + } + +protected: + std::string id_; + rule_spec spec_; + std::unordered_map ancillary_tags_; +}; + +} // namespace ddwaf diff --git a/src/builder/ruleset_builder.cpp b/src/builder/ruleset_builder.cpp new file mode 100644 index 000000000..67b607fae --- /dev/null +++ b/src/builder/ruleset_builder.cpp @@ -0,0 +1,198 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. +// +#include +#include +#include +#include +#include + +#include "builder/action_mapper_builder.hpp" +#include "builder/matcher_builder.hpp" +#include "builder/processor_builder.hpp" +#include "builder/rule_builder.hpp" +#include "configuration/common/configuration.hpp" +#include "exception.hpp" +#include "exclusion/input_filter.hpp" +#include "exclusion/rule_filter.hpp" +#include "indexer.hpp" +#include "log.hpp" +#include "ruleset.hpp" +#include "ruleset_builder.hpp" + +namespace ddwaf { + +namespace { + +template +std::set resolve_references( + const std::vector &references, const indexer &rules) +{ + std::set rule_refs; + if (!references.empty()) { + for (const auto &ref : references) { + if (ref.type == reference_type::id) { + auto *rule = rules.find_by_id(ref.ref_id); + if (rule == nullptr) { + continue; + } + rule_refs.emplace(rule); + } else if (ref.type == reference_type::tags) { + auto current_refs = rules.find_by_tags(ref.tags); + rule_refs.merge(current_refs); + } + } + } else { + // An empty rules reference applies to all rules + for (const auto &rule : rules) { rule_refs.emplace(rule.get()); } + } + return rule_refs; +} + +} // namespace + +std::shared_ptr ruleset_builder::build( + const configuration_spec &global_config, change_set current_changes) +{ + constexpr static change_set base_rule_update = + change_set::rules | change_set::overrides | change_set::actions; + constexpr static change_set custom_rule_update = change_set::custom_rules | change_set::actions; + constexpr static change_set filters_update = + base_rule_update | custom_rule_update | change_set::filters; + constexpr static change_set processors_update = change_set::processors | change_set::scanners; + + if (!actions_ || contains(current_changes, change_set::actions)) { + action_mapper_builder mapper_builder; + for (const auto &[id, spec] : global_config.actions) { + mapper_builder.set_action(id, spec.type_str, spec.parameters); + } + actions_ = mapper_builder.build_shared(); + } + + // When a configuration with 'rules' 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 (contains(current_changes, base_rule_update)) { + final_base_rules_.clear(); + + indexer rule_builders; + // Initially, new rules are generated from their spec + for (const auto &[id, spec] : global_config.base_rules) { + rule_builders.emplace(std::make_shared(id, spec)); + } + + // Overrides only impact base rules since user rules can already be modified by the user + for (const auto &[id, ovrd] : global_config.overrides_by_tags) { + auto rule_builder_targets = resolve_references(ovrd.targets, rule_builders); + for (const auto &rule_builder_ptr : rule_builder_targets) { + rule_builder_ptr->apply_override(ovrd); + } + } + + for (const auto &[id, ovrd] : global_config.overrides_by_id) { + auto rule_builder_targets = resolve_references(ovrd.targets, rule_builders); + for (const auto &rule_builder_ptr : rule_builder_targets) { + rule_builder_ptr->apply_override(ovrd); + } + } + + for (const auto &builder : rule_builders) { + if (builder->is_enabled()) { + final_base_rules_.emplace(builder->build(*actions_)); + } + } + } + + if (contains(current_changes, custom_rule_update)) { + final_user_rules_.clear(); + // Initially, new rules are generated from their spec + for (const auto &[id, spec] : global_config.user_rules) { + rule_builder builder{id, spec}; + if (builder.is_enabled()) { + final_user_rules_.emplace(builder.build(*actions_)); + } + } + } + + // Generate exclusion filters targetting all final rules + if (contains(current_changes, filters_update)) { + rule_filters_.clear(); + input_filters_.clear(); + + // First generate rule filters + for (const auto &[id, filter] : global_config.rule_filters) { + auto rule_targets = resolve_references(filter.targets, final_base_rules_); + rule_targets.merge(resolve_references(filter.targets, final_user_rules_)); + + auto filter_ptr = std::make_shared( + id, filter.expr, std::move(rule_targets), filter.on_match, filter.custom_action); + rule_filters_.emplace(id, filter_ptr); + } + + // Finally input filters + for (const auto &[id, filter] : global_config.input_filters) { + auto rule_targets = resolve_references(filter.targets, final_base_rules_); + rule_targets.merge(resolve_references(filter.targets, final_user_rules_)); + + auto filter_ptr = std::make_shared( + id, filter.expr, std::move(rule_targets), filter.filter); + input_filters_.emplace(id, filter_ptr); + } + } + + // Generate new processors + if (contains(current_changes, processors_update)) { + preprocessors_.clear(); + postprocessors_.clear(); + + for (const auto &[id, spec] : global_config.processors) { + auto proc = processor_builder::build(id, spec, global_config.scanners); + if (spec.evaluate) { + preprocessors_.emplace(proc->get_id(), std::move(proc)); + } else { + postprocessors_.emplace(proc->get_id(), std::move(proc)); + } + } + } + + if (contains(current_changes, change_set::rule_data)) { + rule_matchers_.clear(); + for (const auto &[id, spec] : global_config.rule_data) { + rule_matchers_.emplace(id, matcher_builder::build(spec)); + } + } + + if (contains(current_changes, change_set::exclusion_data)) { + exclusion_matchers_.clear(); + for (const auto &[id, spec] : global_config.exclusion_data) { + exclusion_matchers_.emplace(id, matcher_builder::build(spec)); + } + } + + auto rs = std::make_shared(); + rs->insert_rules(final_base_rules_.items(), final_user_rules_.items()); + rs->insert_filters(rule_filters_); + rs->insert_filters(input_filters_); + rs->insert_preprocessors(preprocessors_); + rs->insert_postprocessors(postprocessors_); + rs->rule_matchers = rule_matchers_; + rs->exclusion_matchers = exclusion_matchers_; + rs->scanners = global_config.scanners.items(); + rs->actions = actions_; + rs->free_fn = free_fn_; + rs->event_obfuscator = event_obfuscator_; + + // An instance is valid if it contains primitives with side-effects, such as + // rules or postprocessors. + if (rs->rules.empty() && rs->postprocessors.empty()) { + DDWAF_WARN("No valid rules or postprocessors found"); + throw parsing_error("no valid or enabled rules or postprocessors found"); + } + + return rs; +} + +} // namespace ddwaf diff --git a/src/ruleset_builder.hpp b/src/builder/ruleset_builder.hpp similarity index 50% rename from src/ruleset_builder.hpp rename to src/builder/ruleset_builder.hpp index 4d163492f..5c50a4b67 100644 --- a/src/ruleset_builder.hpp +++ b/src/builder/ruleset_builder.hpp @@ -7,17 +7,12 @@ #pragma once #include -#include #include -#include -#include "builder/processor_builder.hpp" +#include "configuration/common/configuration.hpp" #include "indexer.hpp" -#include "parameter.hpp" -#include "parser/specification.hpp" #include "rule.hpp" #include "ruleset.hpp" -#include "ruleset_info.hpp" namespace ddwaf { @@ -34,44 +29,16 @@ class ruleset_builder { ruleset_builder &operator=(ruleset_builder &&) = delete; ruleset_builder &operator=(const ruleset_builder &) = delete; - std::shared_ptr build(parameter root_map, base_ruleset_info &info) - { - auto root = static_cast(root_map); - return build(root, info); - } - - std::shared_ptr build(parameter::map &root, base_ruleset_info &info); + std::shared_ptr build( + const configuration_spec &global_config, change_set current_changes); protected: - enum class change_state : uint32_t { - none = 0, - rules = 1, - custom_rules = 2, - overrides = 4, - filters = 8, - rule_data = 16, - processors = 32, - scanners = 64, - actions = 128, - exclusion_data = 256, - }; - - 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, base_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_; + object_limits limits_; + ddwaf_object_free_fn free_fn_; std::shared_ptr event_obfuscator_; - // Map representing rule data IDs to matcher type, this is obtained - // from parsing the ruleset ('rules' key). - std::unordered_map rule_data_ids_; - std::unordered_map filter_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. @@ -79,22 +46,6 @@ class ruleset_builder { // 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 'custom_rules' - parser::rule_spec_container user_rules_; - // Obtained from 'rules_data', depends on base_rules_ - parser::matcher_container rule_matchers_; - // Obtained from 'rules_override' - parser::override_spec_container overrides_; - // Obtained from 'exclusions' - parser::filter_spec_container exclusions_; - // Obtained from 'exclusion_data', depends on exclusions_ - parser::matcher_container exclusion_matchers_; - // Obtained from 'processors' - processor_container processors_; - // These are the contents of the latest generated ruleset - // Rules indexer final_base_rules_; indexer final_user_rules_; @@ -107,11 +58,12 @@ class ruleset_builder { std::unordered_map> preprocessors_; std::unordered_map> postprocessors_; - // Scanners - indexer scanners_; + // Matchers + std::unordered_map> rule_matchers_; + std::unordered_map> exclusion_matchers_; // Actions - std::shared_ptr actions_; + std::shared_ptr actions_; }; } // namespace ddwaf diff --git a/src/builder/waf_builder.hpp b/src/builder/waf_builder.hpp new file mode 100644 index 000000000..9aa691b8f --- /dev/null +++ b/src/builder/waf_builder.hpp @@ -0,0 +1,50 @@ +// 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 "builder/ruleset_builder.hpp" +#include "configuration/configuration_manager.hpp" +#include "waf.hpp" + +namespace ddwaf { + +class waf_builder { +public: + waf_builder(object_limits limits, ddwaf_object_free_fn free_fn, + std::shared_ptr event_obfuscator) + : rbuilder_(limits, free_fn, std::move(event_obfuscator)) + {} + + ~waf_builder() = default; + waf_builder(waf_builder &&) = delete; + waf_builder(const waf_builder &) = delete; + waf_builder &operator=(waf_builder &&) = delete; + waf_builder &operator=(const waf_builder &) = delete; + + bool add_or_update(const std::string &path, parameter::map &root, base_ruleset_info &info) + { + return cfg_mgr_.add_or_update(path, root, info); + } + + bool remove(const std::string &path) { return cfg_mgr_.remove(path); } + + ddwaf::waf build() + { + auto [global_config, current_changes] = cfg_mgr_.consolidate(); + auto ruleset = rbuilder_.build(global_config, current_changes); + return waf{std::move(ruleset)}; + } + +protected: + configuration_manager cfg_mgr_; + ruleset_builder rbuilder_; +}; + +} // namespace ddwaf diff --git a/src/condition/cmdi_detector.cpp b/src/condition/cmdi_detector.cpp index 8dfd81367..f52d652a4 100644 --- a/src/condition/cmdi_detector.cpp +++ b/src/condition/cmdi_detector.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. #include +#include #include #include #include @@ -89,16 +90,56 @@ struct opt_spec { // shells such as ksh allow for the first argument to be a shell command std::unordered_map known_shells{ // sh could be bash (red-hat) or dash (debian) so we cast a wide net - {"sh", {true, false, platform::linux, {"c"}, {"O", "o", "init-file", "rcfile"}}}, - {"bash", {true, false, platform::linux, {"c"}, {"O", "o", "init-file", "rcfile"}}}, - {"ksh", {false, false, platform::linux, {"c"}, {"o", "T"}}}, - {"rksh", {false, false, platform::linux, {"c"}, {"o", "T"}}}, - {"fish", {true, true, platform::linux, {"c", "command"}, {}}}, - {"zsh", {true, false, platform::linux, {"c"}, {"o"}}}, - {"dash", {true, false, platform::linux, {"c"}, {"o"}}}, - {"ash", {true, false, platform::linux, {"c"}, {"o"}}}, - {"powershell", {true, true, platform::windows, {"command", "commandwithargs"}, {}}}, - {"pwsh", {true, true, platform::windows, {"command", "commandwithargs"}, {}}}, + {"sh", {.requires_command_opt = true, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"O", "o", "init-file", "rcfile"}}}, + {"bash", {.requires_command_opt = true, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"O", "o", "init-file", "rcfile"}}}, + {"ksh", {.requires_command_opt = false, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"o", "T"}}}, + {"rksh", {.requires_command_opt = false, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"o", "T"}}}, + {"fish", {.requires_command_opt = true, + .command_after_opt = true, + .shell_platform = platform::linux, + .command_opt = {"c", "command"}, + .opts_with_arg = {}}}, + {"zsh", {.requires_command_opt = true, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"o"}}}, + {"dash", {.requires_command_opt = true, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"o"}}}, + {"ash", {.requires_command_opt = true, + .command_after_opt = false, + .shell_platform = platform::linux, + .command_opt = {"c"}, + .opts_with_arg = {"o"}}}, + {"powershell", {.requires_command_opt = true, + .command_after_opt = true, + .shell_platform = platform::windows, + .command_opt = {"command", "commandwithargs"}, + .opts_with_arg = {}}}, + {"pwsh", {.requires_command_opt = true, + .command_after_opt = true, + .shell_platform = platform::windows, + .command_opt = {"command", "commandwithargs"}, + .opts_with_arg = {}}}, }; std::string_view basename(std::string_view path) @@ -151,7 +192,7 @@ std::string_view object_at(const ddwaf_object &obj, std::size_t idx) return {child.stringValue, object_size(child)}; } -enum class opt_type { none, short_opt, long_opt, end_opt }; +enum class opt_type : uint8_t { none, short_opt, long_opt, end_opt }; inline std::pair split_long_opt_with_arg(std::string_view opt) { @@ -360,7 +401,7 @@ std::optional cmdi_impl(const ddwaf_object &exec_args, // When the full binary has been injected, we consider it an exploit // although bear in mind that this can also be a vulnerable-by-design // application, leading to a false positive - return {{std::string(value), it.get_current_path()}}; + return {{.value = std::string(value), .key_path = it.get_current_path()}}; } } @@ -412,7 +453,7 @@ eval_result cmdi_detector::eval_impl(const unary_argument auto res = cmdi_impl( *resource.value, resource_tokens, *param.value, objects_excluded, limits_, deadline); if (res.has_value()) { - std::vector resource_kp{ + const std::vector resource_kp{ resource.key_path.begin(), resource.key_path.end()}; const bool ephemeral = resource.ephemeral || param.ephemeral; @@ -420,12 +461,21 @@ eval_result cmdi_detector::eval_impl(const unary_argument DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); - cache.match = condition_match{{{"resource"sv, generate_string_resource(*resource.value), - resource.address, resource_kp}, - {"params"sv, highlight, param.address, param_kp}}, - {std::move(highlight)}, "cmdi_detector"sv, {}, ephemeral}; - - return {true, ephemeral}; + cache.match = + condition_match{.args = {{.name = "resource"sv, + .resolved = generate_string_resource(*resource.value), + .address = resource.address, + .key_path = resource_kp}, + {.name = "params"sv, + .resolved = highlight, + .address = param.address, + .key_path = param_kp}}, + .highlights = {std::move(highlight)}, + .operator_name = "cmdi_detector"sv, + .operator_value = {}, + .ephemeral = ephemeral}; + + return {.outcome = true, .ephemeral = ephemeral}; } } diff --git a/src/condition/exists.cpp b/src/condition/exists.cpp index d96fb4256..de9e05785 100644 --- a/src/condition/exists.cpp +++ b/src/condition/exists.cpp @@ -25,7 +25,7 @@ namespace ddwaf { namespace { -enum class search_outcome { found, not_found, unknown }; +enum class search_outcome : uint8_t { found, not_found, unknown }; const ddwaf_object *find_key( const ddwaf_object &parent, std::string_view key, const object_limits &limits) @@ -98,12 +98,18 @@ search_outcome exists(const ddwaf_object *root, std::span key if (exists(input.value, input.key_path, objects_excluded, limits_) == search_outcome::found) { std::vector key_path{input.key_path.begin(), input.key_path.end()}; - cache.match = {{{{"input", {}, input.address, std::move(key_path)}}, {}, "exists", {}, - input.ephemeral}}; - return {true, input.ephemeral}; + cache.match = {{.args = {{.name = "input", + .resolved = {}, + .address = input.address, + .key_path = std::move(key_path)}}, + .highlights = {}, + .operator_name = "exists", + .operator_value = {}, + .ephemeral = input.ephemeral}}; + return {.outcome = true, .ephemeral = input.ephemeral}; } } - return {false, false}; + return {.outcome = false, .ephemeral = false}; } [[nodiscard]] eval_result exists_negated_condition::eval_impl( @@ -115,13 +121,19 @@ search_outcome exists(const ddwaf_object *root, std::span key // the data set if (exists(input.value, input.key_path, objects_excluded, limits_) != search_outcome::not_found) { - return {false, false}; + return {.outcome = false, .ephemeral = false}; } std::vector key_path{input.key_path.begin(), input.key_path.end()}; - cache.match = { - {{{"input", {}, input.address, std::move(key_path)}}, {}, "!exists", {}, input.ephemeral}}; - return {true, input.ephemeral}; + cache.match = {{.args = {{.name = "input", + .resolved = {}, + .address = input.address, + .key_path = std::move(key_path)}}, + .highlights = {}, + .operator_name = "!exists", + .operator_value = {}, + .ephemeral = input.ephemeral}}; + return {.outcome = true, .ephemeral = input.ephemeral}; } } // namespace ddwaf diff --git a/src/condition/lfi_detector.cpp b/src/condition/lfi_detector.cpp index ed8d06cfc..84b681084 100644 --- a/src/condition/lfi_detector.cpp +++ b/src/condition/lfi_detector.cpp @@ -129,19 +129,27 @@ eval_result lfi_detector::eval_impl(const unary_argument &path for (const auto ¶m : params) { auto res = lfi_impl(path.value, *param.value, objects_excluded, limits_, deadline); if (res.has_value()) { - std::vector path_kp{path.key_path.begin(), path.key_path.end()}; + const std::vector path_kp{path.key_path.begin(), path.key_path.end()}; const bool ephemeral = path.ephemeral || param.ephemeral; auto &[highlight, param_kp] = res.value(); DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); - cache.match = - condition_match{{{"resource"sv, std::string{path.value}, path.address, path_kp}, - {"params"sv, highlight, param.address, param_kp}}, - {std::move(highlight)}, "lfi_detector", {}, ephemeral}; - - return {true, path.ephemeral || param.ephemeral}; + cache.match = condition_match{.args = {{.name = "resource"sv, + .resolved = std::string{path.value}, + .address = path.address, + .key_path = path_kp}, + {.name = "params"sv, + .resolved = highlight, + .address = param.address, + .key_path = param_kp}}, + .highlights = {std::move(highlight)}, + .operator_name = "lfi_detector", + .operator_value = {}, + .ephemeral = ephemeral}; + + return {.outcome = true, .ephemeral = path.ephemeral || param.ephemeral}; } } diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index 940f4835c..ea924b1b0 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -222,9 +222,14 @@ eval_result scalar_negated_condition::eval(condition_cache &cache, const object_ } if (!match) { - cache.match = {{{{"input"sv, object_to_string(*object), target_.name, - {target_.key_path.begin(), target_.key_path.end()}}}, - {}, matcher->negated_name(), matcher->to_string(), ephemeral}}; + cache.match = {{.args = {{.name = "input"sv, + .resolved = object_to_string(*object), + .address = target_.name, + .key_path = {target_.key_path.begin(), target_.key_path.end()}}}, + .highlights = {}, + .operator_name = matcher->negated_name(), + .operator_value = matcher->to_string(), + .ephemeral = ephemeral}}; return {.outcome = true, .ephemeral = ephemeral}; } diff --git a/src/condition/shi_detector.cpp b/src/condition/shi_detector.cpp index cb3057a1c..0a50cc0a0 100644 --- a/src/condition/shi_detector.cpp +++ b/src/condition/shi_detector.cpp @@ -43,7 +43,7 @@ eval_result shi_detector::eval_string(const unary_argument auto res = find_shi_from_params( resource_sv, resource_tokens, *param.value, objects_excluded, limits_, deadline); if (res.has_value()) { - std::vector resource_kp{ + const std::vector resource_kp{ resource.key_path.begin(), resource.key_path.end()}; const bool ephemeral = resource.ephemeral || param.ephemeral; @@ -51,12 +51,20 @@ eval_result shi_detector::eval_string(const unary_argument DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); - cache.match = condition_match{ - {{"resource"sv, std::string{resource_sv}, resource.address, resource_kp}, - {"params"sv, highlight, param.address, param_kp}}, - {std::move(highlight)}, "shi_detector", {}, ephemeral}; - - return {true, ephemeral}; + cache.match = condition_match{.args = {{.name = "resource"sv, + .resolved = std::string{resource_sv}, + .address = resource.address, + .key_path = resource_kp}, + {.name = "params"sv, + .resolved = highlight, + .address = param.address, + .key_path = param_kp}}, + .highlights = {std::move(highlight)}, + .operator_name = "shi_detector", + .operator_value = {}, + .ephemeral = ephemeral}; + + return {.outcome = true, .ephemeral = ephemeral}; } } @@ -77,7 +85,7 @@ eval_result shi_detector::eval_array(const unary_argument auto res = find_shi_from_params( arguments, resource_tokens, *param.value, objects_excluded, limits_, deadline); if (res.has_value()) { - std::vector resource_kp{ + const std::vector resource_kp{ resource.key_path.begin(), resource.key_path.end()}; const bool ephemeral = resource.ephemeral || param.ephemeral; @@ -85,12 +93,20 @@ eval_result shi_detector::eval_array(const unary_argument DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); - cache.match = condition_match{ - {{"resource"sv, std::move(arguments.resource), resource.address, resource_kp}, - {"params"sv, highlight, param.address, param_kp}}, - {std::move(highlight)}, "shi_detector", {}, ephemeral}; - - return {true, ephemeral}; + cache.match = condition_match{.args = {{.name = "resource"sv, + .resolved = std::move(arguments.resource), + .address = resource.address, + .key_path = resource_kp}, + {.name = "params"sv, + .resolved = highlight, + .address = param.address, + .key_path = param_kp}}, + .highlights = {std::move(highlight)}, + .operator_name = "shi_detector", + .operator_value = {}, + .ephemeral = ephemeral}; + + return {.outcome = true, .ephemeral = ephemeral}; } } diff --git a/src/condition/sqli_detector.cpp b/src/condition/sqli_detector.cpp index 6eb96c6b7..7f624ef0a 100644 --- a/src/condition/sqli_detector.cpp +++ b/src/condition/sqli_detector.cpp @@ -3,7 +3,9 @@ // // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include #include +#include #include #include #include @@ -39,7 +41,7 @@ namespace { constexpr auto &npos = std::string_view::npos; constexpr std::size_t min_token_count = 4; -enum class sqli_error { +enum class sqli_error : uint8_t { none, invalid_sql, }; @@ -209,7 +211,7 @@ bool is_where_tautology(const std::vector &resource_tokens, bool has_order_by_structure(std::span tokens) { - enum class order_by_state { + enum class order_by_state : uint8_t { invalid, begin, table_or_column_name, @@ -409,9 +411,7 @@ std::pair, std::size_t> get_consecutive_tokens( auto rtoken_end = rtoken.index + rtoken.str.size(); if (rtoken_end > begin) { if (rtoken.index < end) { - if (i < index_begin) { - index_begin = i; - } + index_begin = std::min(i, index_begin); if (i > index_end || i == (resource_tokens.size() - 1)) { index_end = i; @@ -520,7 +520,7 @@ sqli_result sqli_impl(std::string_view resource, std::vector &resourc auto res = internal::sqli_impl( sql.value, resource_tokens, *param.value, dialect, objects_excluded, limits_, deadline); if (std::holds_alternative(res)) { - std::vector sql_kp{sql.key_path.begin(), sql.key_path.end()}; + const std::vector sql_kp{sql.key_path.begin(), sql.key_path.end()}; const bool ephemeral = sql.ephemeral || param.ephemeral; auto stripped_stmt = internal::strip_literals(sql.value, resource_tokens); @@ -529,13 +529,24 @@ sqli_result sqli_impl(std::string_view resource, std::vector &resourc DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); - cache.match = - condition_match{{{"resource"sv, stripped_stmt, sql.address, sql_kp}, - {"params"sv, highlight, param.address, param_kp}, - {"db_type"sv, std::string{db_type.value}, db_type.address, {}}}, - {std::move(highlight)}, "sqli_detector", {}, ephemeral}; - - return {true, ephemeral}; + cache.match = condition_match{.args = {{.name = "resource"sv, + .resolved = stripped_stmt, + .address = sql.address, + .key_path = sql_kp}, + {.name = "params"sv, + .resolved = highlight, + .address = param.address, + .key_path = param_kp}, + {.name = "db_type"sv, + .resolved = std::string{db_type.value}, + .address = db_type.address, + .key_path = {}}}, + .highlights = {std::move(highlight)}, + .operator_name = "sqli_detector", + .operator_value = {}, + .ephemeral = ephemeral}; + + return {.outcome = true, .ephemeral = ephemeral}; } if (std::holds_alternative(res)) { diff --git a/src/condition/ssrf_detector.cpp b/src/condition/ssrf_detector.cpp index d240d0a39..238bd7071 100644 --- a/src/condition/ssrf_detector.cpp +++ b/src/condition/ssrf_detector.cpp @@ -269,19 +269,27 @@ eval_result ssrf_detector::eval_impl(const unary_argument &uri auto res = ssrf_impl(*decomposed, *param.value, objects_excluded, limits_, dangerous_ip_matcher_, authorised_schemes_, deadline); if (res.has_value()) { - std::vector uri_kp{uri.key_path.begin(), uri.key_path.end()}; + const std::vector uri_kp{uri.key_path.begin(), uri.key_path.end()}; const bool ephemeral = uri.ephemeral || param.ephemeral; auto &[highlight, param_kp] = res.value(); DDWAF_TRACE("Target {} matched parameter value {}", param.address, highlight); - cache.match = - condition_match{{{"resource"sv, std::string{uri.value}, uri.address, uri_kp}, - {"params"sv, highlight, param.address, param_kp}}, - {std::move(highlight)}, "ssrf_detector", {}, ephemeral}; - - return {true, ephemeral}; + cache.match = condition_match{.args = {{.name = "resource"sv, + .resolved = std::string{uri.value}, + .address = uri.address, + .key_path = uri_kp}, + {.name = "params"sv, + .resolved = highlight, + .address = param.address, + .key_path = param_kp}}, + .highlights = {std::move(highlight)}, + .operator_name = "ssrf_detector", + .operator_value = {}, + .ephemeral = ephemeral}; + + return {.outcome = true, .ephemeral = ephemeral}; } } diff --git a/src/parser/actions_parser.cpp b/src/configuration/actions_parser.cpp similarity index 56% rename from src/parser/actions_parser.cpp rename to src/configuration/actions_parser.cpp index ad111bc89..2942de285 100644 --- a/src/parser/actions_parser.cpp +++ b/src/configuration/actions_parser.cpp @@ -5,21 +5,25 @@ // Copyright 2021 Datadog, Inc. #include -#include #include #include #include #include "action_mapper.hpp" +#include "builder/action_mapper_builder.hpp" +#include "configuration/actions_parser.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" #include "log.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" #include "uri_utils.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { -void validate_and_add_block(auto &id, auto &type, auto ¶meters, action_mapper_builder &builder) +namespace { + +void validate_and_add_block(auto &cfg, auto id, auto &type, auto ¶meters) { if (!parameters.contains("status_code") || !parameters.contains("grpc_status_code") || !parameters.contains("type")) { @@ -28,16 +32,20 @@ void validate_and_add_block(auto &id, auto &type, auto ¶meters, action_mappe auto default_params = action_mapper_builder::get_default_action("block"); for (const auto &[k, v] : default_params.parameters) { parameters.try_emplace(k, v); } } - builder.set_action(id, std::move(type), std::move(parameters)); + + cfg.emplace_action(std::move(id), + action_spec{action_type_from_string(type), std::move(type), std::move(parameters)}); } -void validate_and_add_redirect( - auto &id, auto &type, auto ¶meters, action_mapper_builder &builder) +void validate_and_add_redirect(auto &cfg, auto id, auto &type, auto ¶meters) { auto it = parameters.find("location"); if (it == parameters.end() || it->second.empty()) { - // TODO add a log message - builder.alias_default_action_to("block", id); + auto block_params = action_mapper_builder::get_default_action("block"); + DDWAF_DEBUG("Location missing from redirect action '{}', downgrading to block_request", id); + cfg.emplace_action(id, action_spec{.type = block_params.type, + .type_str = block_params.type_str, + .parameters = block_params.parameters}); return; } @@ -52,7 +60,12 @@ void validate_and_add_redirect( (!decomposed->scheme.empty() && decomposed->scheme != "http" && decomposed->scheme != "https") || (decomposed->scheme_and_authority.empty() && !decomposed->path.starts_with('/'))) { - builder.alias_default_action_to("block", id); + auto block_params = action_mapper_builder::get_default_action("block"); + + DDWAF_DEBUG("Unsupported scheme on redirect action '{}', downgrading to block_request", id); + cfg.emplace_action(id, action_spec{.type = block_params.type, + .type_str = block_params.type_str, + .parameters = block_params.parameters}); return; } @@ -66,14 +79,15 @@ void validate_and_add_redirect( parameters.emplace("status_code", "303"); } - builder.set_action(id, std::move(type), std::move(parameters)); + cfg.emplace_action( + id, action_spec{action_type_from_string(type), std::move(type), std::move(parameters)}); } -std::shared_ptr parse_actions( - parameter::vector &actions_array, base_section_info &info) -{ - action_mapper_builder builder; +} // namespace +void parse_actions( + const parameter::vector &actions_array, configuration_collector &cfg, base_section_info &info) +{ for (unsigned i = 0; i < actions_array.size(); i++) { const auto &node_param = actions_array[i]; auto node = static_cast(node_param); @@ -81,6 +95,12 @@ std::shared_ptr parse_actions( std::string id; try { id = at(node, "id"); + if (cfg.contains_action(id)) { + DDWAF_WARN("Duplicate action: {}", id); + info.add_failed(id, "duplicate action"); + continue; + } + auto type = at(node, "type"); auto parameters = at>(node, "parameters"); @@ -88,14 +108,16 @@ std::shared_ptr parse_actions( // Block and redirect actions should be validated and aliased if (type == "redirect_request") { - validate_and_add_redirect(id, type, parameters, builder); + validate_and_add_redirect(cfg, id, type, parameters); } else if (type == "block_request") { - validate_and_add_block(id, type, parameters, builder); + validate_and_add_block(cfg, id, type, parameters); } else { - builder.set_action(id, std::move(type), std::move(parameters)); + cfg.emplace_action(id, action_spec{.type = action_type_from_string(type), + .type_str = std::move(type), + .parameters = std::move(parameters)}); } - info.add_loaded(id); + info.add_loaded(std::move(id)); } catch (const std::exception &e) { if (id.empty()) { id = index_to_id(i); @@ -104,8 +126,6 @@ std::shared_ptr parse_actions( info.add_failed(id, e.what()); } } - - return builder.build_shared(); } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/actions_parser.hpp b/src/configuration/actions_parser.hpp new file mode 100644 index 000000000..bcd5411b7 --- /dev/null +++ b/src/configuration/actions_parser.hpp @@ -0,0 +1,22 @@ +// 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 "action_mapper.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +void parse_actions( + const parameter::vector &actions_array, configuration_collector &cfg, base_section_info &info); + +} // namespace ddwaf diff --git a/src/parser/common.hpp b/src/configuration/common/common.hpp similarity index 69% rename from src/parser/common.hpp rename to src/configuration/common/common.hpp index 3f0f3cb8c..b9b696754 100644 --- a/src/parser/common.hpp +++ b/src/configuration/common/common.hpp @@ -6,23 +6,15 @@ #pragma once -#include #include #include "exception.hpp" #include "parameter.hpp" -#include "parser/specification.hpp" #include "ruleset_info.hpp" -#include "transformer/base.hpp" using base_section_info = ddwaf::base_ruleset_info::base_section_info; -namespace ddwaf::parser { - -struct address_container { - std::unordered_set required; - std::unordered_set optional; -}; +namespace ddwaf { template T at(const parameter::map &map, const Key &key) { @@ -46,11 +38,12 @@ T at(const parameter::map &map, const Key &key, const T &default_) } } -std::optional transformer_from_string(std::string_view str); - inline std::string index_to_id(unsigned idx) { return "index:" + to_string(idx); } -reference_spec parse_reference(const parameter::map &target); +struct address_container { + std::unordered_set required; + std::unordered_set optional; +}; inline void add_addresses_to_info(const address_container &addresses, base_section_info &info) { @@ -58,4 +51,27 @@ inline void add_addresses_to_info(const address_container &addresses, base_secti for (const auto &address : addresses.optional) { info.add_optional_address(address); } } -} // namespace ddwaf::parser +inline unsigned parse_schema_version(parameter::map &ruleset) +{ + auto version = at(ruleset, "version", {}); + if (version.empty()) { + return 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); + + 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 diff --git a/src/configuration/common/configuration.hpp b/src/configuration/common/configuration.hpp new file mode 100644 index 000000000..fc45a444b --- /dev/null +++ b/src/configuration/common/configuration.hpp @@ -0,0 +1,187 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/). Copyright 2024 Datadog, Inc. + +#pragma once + +#include +#include +#include +#include + +#include "action_mapper.hpp" +#include "exclusion/object_filter.hpp" +#include "indexed_multivector.hpp" +#include "indexer.hpp" +#include "processor/base.hpp" +#include "rule.hpp" +#include "scanner.hpp" + +namespace ddwaf { + +struct rule_spec { + bool enabled; + core_rule::source_type source; + std::string name; + std::unordered_map tags; + std::shared_ptr expr; + std::vector actions; +}; + +enum class reference_type { none, id, tags }; + +struct reference_spec { + reference_type type; + std::string ref_id; + std::unordered_map tags; +}; + +struct override_spec { + reference_type type; + std::optional enabled; + std::optional> actions; + std::vector targets; + std::unordered_map tags; +}; + +struct rule_filter_spec { + std::shared_ptr expr; + std::vector targets; + exclusion::filter_mode on_match; + std::string custom_action; +}; + +struct input_filter_spec { + std::shared_ptr expr; + std::shared_ptr filter; + std::vector targets; +}; + +enum class processor_type : unsigned { + extract_schema, + http_endpoint_fingerprint, + http_network_fingerprint, + http_header_fingerprint, + session_fingerprint, +}; + +struct processor_spec { + processor_type type; + std::shared_ptr expr; + std::vector mappings; + std::vector scanners; + bool evaluate{false}; + bool output{true}; +}; + +enum class data_type : uint8_t { unknown, data_with_expiration, ip_with_expiration }; + +struct data_spec { + using value_type = std::pair; + data_type type{data_type::unknown}; + indexed_multivector values; +}; + +struct action_spec { + action_type type; + std::string type_str; + std::unordered_map parameters; +}; + +enum class change_set : uint16_t { + none = 0, + rules = 1, + custom_rules = 2, + overrides = 4, + filters = 8, + rule_data = 16, + processors = 32, + scanners = 64, + actions = 128, + exclusion_data = 256, +}; + +// NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) +constexpr change_set operator|(change_set lhs, change_set rhs) +{ + return static_cast(static_cast>(lhs) | + static_cast>(rhs)); +} + +constexpr change_set &operator|=(change_set &lhs, change_set rhs) { return lhs = lhs | rhs; } + +constexpr change_set operator&(change_set lhs, change_set rhs) +{ + return static_cast(static_cast>(lhs) & + static_cast>(rhs)); +} + +constexpr change_set &operator&=(change_set &lhs, change_set rhs) { return lhs = lhs & rhs; } + +constexpr bool contains(change_set set, change_set opt) { return (set & opt) != change_set::none; } +// NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) + +// The configuration_change_spec structure contains the IDs of all elements +// introduced by the given configuration. This can be used later on to remove +// the contents from the global configuration. +struct configuration_change_spec { + [[nodiscard]] bool empty() const { return content == change_set::none; } + + // Specifies the contents of the configuration + change_set content{change_set::none}; + // Rule IDs + std::unordered_set base_rules; + std::unordered_set user_rules; + // Rule data IDs consist of a pair containing the data ID and a given unique + // ID for the given data spec. + std::vector> rule_data; + // Override IDs consisting of a unique ID auto-generated for each override + std::unordered_set overrides_by_id; + std::unordered_set overrides_by_tags; + // Filter IDs + std::unordered_set rule_filters; + std::unordered_set input_filters; + // Exclusion data IDs consist of a pair containing the data ID and a given + // unique ID for the given data spec. + std::vector> exclusion_data; + // Processor IDs + std::unordered_set processors; + // Scanner IDs + std::unordered_set scanners; + // Action IDs + std::unordered_set actions; +}; + +// The configuration spec is a global configuration structure which is generated +// from all the partial configurations. Each item in this configuration has its +// own unique ID which is mapped to the independent configuration so that it can +// be later removed as needed. +struct configuration_spec { + // Obtained from 'rules' + std::unordered_map base_rules; + // Obtained from 'custom_rules' + std::unordered_map user_rules; + // Obtained from 'rules_data', depends on base_rules_ + std::unordered_map rule_data; + // Obtained from 'rules_override' + // The distinction is only necessary due to the restriction that + // overrides by ID are to be considered a priority over overrides by tags + std::unordered_map overrides_by_id; + std::unordered_map overrides_by_tags; + // Obtained from 'exclusions' + std::unordered_map rule_filters; + std::unordered_map input_filters; + // Obtained from 'exclusion_data', depends on exclusions_ + std::unordered_map exclusion_data; + // Obtained from 'processors' + std::unordered_map processors; + // Obtained from 'scanners' + // Scanners are stored directly in an indexer to simplify their use + indexer scanners; + // Obtained from 'actions' + std::unordered_map actions; +}; + +} // namespace ddwaf diff --git a/src/configuration/common/configuration_collector.hpp b/src/configuration/common/configuration_collector.hpp new file mode 100644 index 000000000..62ab4eea3 --- /dev/null +++ b/src/configuration/common/configuration_collector.hpp @@ -0,0 +1,157 @@ +// 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 2025 Datadog, Inc. + +#pragma once + +#include "configuration/common/configuration.hpp" + +namespace ddwaf { + +class configuration_collector { +public: + configuration_collector(configuration_change_spec &change, configuration_spec &config) + : change_(change), config_(config){}; + configuration_collector(const configuration_collector &) = delete; + configuration_collector &operator=(const configuration_collector &) = delete; + configuration_collector(configuration_collector &&) = delete; + configuration_collector &operator=(configuration_collector &&) = delete; + ~configuration_collector() = default; + + [[nodiscard]] bool contains_rule(const std::string &id) const + { + return config_.base_rules.contains(id) || config_.user_rules.contains(id); + } + + [[nodiscard]] bool contains_filter(const std::string &id) const + { + return config_.rule_filters.contains(id) || config_.input_filters.contains(id); + } + + [[nodiscard]] bool contains_processor(const std::string &id) const + { + return config_.processors.contains(id); + } + + [[nodiscard]] bool contains_scanner(const std::string &id) const + { + return config_.scanners.contains(id); + } + + [[nodiscard]] bool contains_action(const std::string &id) const + { + return config_.actions.contains(id); + } + + void emplace_rule(std::string id, rule_spec spec) + { + if (spec.source == core_rule::source_type::base) { + change_.content |= change_set::rules; + + change_.base_rules.emplace(id); + config_.base_rules.emplace(std::move(id), std::move(spec)); + } else { + change_.content |= change_set::custom_rules; + + change_.user_rules.emplace(id); + config_.user_rules.emplace(std::move(id), std::move(spec)); + } + } + + void emplace_override(std::string id, override_spec spec) + { + change_.content |= change_set::overrides; + + if (spec.type == reference_type::id) { + change_.overrides_by_id.emplace(id); + config_.overrides_by_id.emplace(std::move(id), std::move(spec)); + } else if (spec.type == reference_type::tags) { + change_.overrides_by_tags.emplace(id); + config_.overrides_by_tags.emplace(std::move(id), std::move(spec)); + } + } + + void emplace_filter(std::string id, rule_filter_spec spec) + { + change_.content |= change_set::filters; + + change_.rule_filters.emplace(id); + config_.rule_filters.emplace(std::move(id), std::move(spec)); + } + + void emplace_filter(std::string id, input_filter_spec spec) + { + change_.content |= change_set::filters; + + change_.input_filters.emplace(id); + config_.input_filters.emplace(std::move(id), std::move(spec)); + } + + void emplace_processor(std::string id, processor_spec spec) + { + change_.content |= change_set::processors; + + change_.processors.emplace(id); + config_.processors.emplace(std::move(id), std::move(spec)); + } + + void emplace_scanner(std::shared_ptr scanner) + { + change_.content |= change_set::scanners; + + change_.scanners.emplace(scanner->get_id()); + config_.scanners.emplace(std::move(scanner)); + } + + void emplace_action(std::string id, action_spec spec) + { + change_.content |= change_set::actions; + + change_.actions.emplace(id); + config_.actions.emplace(std::move(id), std::move(spec)); + } + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + void emplace_rule_data(std::string data_id, std::string id, data_type type, + std::vector values) + { + change_.content |= change_set::rule_data; + + auto it = config_.rule_data.find(data_id); + if (it == config_.rule_data.end()) { + auto &spec = config_.rule_data[data_id]; + spec.type = type; + spec.values.emplace(id, std::move(values)); + } else { + // TODO fail if type differs + it->second.values.emplace(id, std::move(values)); + } + change_.rule_data.emplace_back(std::move(data_id), std::move(id)); + } + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + void emplace_exclusion_data(std::string data_id, std::string id, data_type type, + std::vector values) + { + change_.content |= change_set::exclusion_data; + + auto it = config_.exclusion_data.find(data_id); + if (it == config_.exclusion_data.end()) { + auto &spec = config_.exclusion_data[data_id]; + spec.type = type; + spec.values.emplace(id, std::move(values)); + } else { + // TODO fail if type differs + it->second.values.emplace(id, std::move(values)); + } + change_.exclusion_data.emplace_back(std::move(data_id), std::move(id)); + } + +protected: + configuration_change_spec &change_; + configuration_spec &config_; +}; + +} // namespace ddwaf diff --git a/src/parser/expression_parser.cpp b/src/configuration/common/expression_parser.cpp similarity index 85% rename from src/parser/expression_parser.cpp rename to src/configuration/common/expression_parser.cpp index c3fe1cd9c..2729a17f6 100644 --- a/src/parser/expression_parser.cpp +++ b/src/configuration/common/expression_parser.cpp @@ -18,6 +18,10 @@ #include "condition/shi_detector.hpp" #include "condition/sqli_detector.hpp" #include "condition/ssrf_detector.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/expression_parser.hpp" +#include "configuration/common/matcher_parser.hpp" +#include "configuration/common/transformer_parser.hpp" #include "exception.hpp" #include "expression.hpp" #include "log.hpp" @@ -31,14 +35,11 @@ #include "matcher/phrase_match.hpp" #include "matcher/regex_match.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/matcher_parser.hpp" -#include "parser/parser.hpp" #include "target_address.hpp" #include "transformer/base.hpp" #include "utils.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { namespace { @@ -93,8 +94,11 @@ std::vector parse_arguments(const parameter::map ¶ms, d addresses.required.emplace(address); auto it = input.find("transformers"); if (it == input.end()) { - targets.emplace_back(condition_target{ - address, get_target_index(address), std::move(kp), transformers, source}); + targets.emplace_back(condition_target{.name = address, + .index = get_target_index(address), + .key_path = std::move(kp), + .transformers = transformers, + .source = source}); } else { auto input_transformers = static_cast(it->second); if (input_transformers.size() > limits.max_transformers_per_address) { @@ -103,8 +107,11 @@ std::vector parse_arguments(const parameter::map ¶ms, d source = data_source::values; auto new_transformers = parse_transformers(input_transformers, source); - targets.emplace_back(condition_target{address, get_target_index(address), - std::move(kp), std::move(new_transformers), source}); + targets.emplace_back(condition_target{.name = address, + .index = get_target_index(address), + .key_path = std::move(kp), + .transformers = std::move(new_transformers), + .source = source}); } } } @@ -114,16 +121,10 @@ std::vector parse_arguments(const parameter::map ¶ms, d template auto build_condition(std::string_view operator_name, const parameter::map ¶ms, - std::unordered_map &data_ids_to_type, data_source source, - const std::vector &transformers, address_container &addresses, - const object_limits &limits) + data_source source, const std::vector &transformers, + address_container &addresses, const object_limits &limits) { auto [data_id, matcher] = parse_matcher(operator_name, params); - - if (!matcher && !data_id.empty()) { - data_ids_to_type.emplace(data_id, operator_name); - } - auto arguments = parse_arguments(params, source, transformers, addresses, limits); return std::make_unique(std::move(matcher), data_id, std::move(arguments), limits); } @@ -145,9 +146,8 @@ auto build_versioned_condition(std::string_view operator_name, unsigned version, } // namespace std::shared_ptr parse_expression(const parameter::vector &conditions_array, - std::unordered_map &data_ids_to_type, data_source source, - const std::vector &transformers, address_container &addresses, - const object_limits &limits) + data_source source, const std::vector &transformers, + address_container &addresses, const object_limits &limits) { std::vector> conditions; for (const auto &cond_param : conditions_array) { @@ -197,15 +197,13 @@ std::shared_ptr parse_expression(const parameter::vector &conditions conditions.emplace_back( build_condition>( - operator_name.substr(1), params, data_ids_to_type, source, transformers, - addresses, limits)); + operator_name.substr(1), params, source, transformers, addresses, limits)); } else { conditions.emplace_back( build_condition, matcher::exact_match, matcher::greater_than<>, matcher::ip_match, matcher::is_sqli, matcher::is_xss, matcher::lower_than<>, matcher::phrase_match, matcher::regex_match>( - operator_name, params, data_ids_to_type, source, transformers, addresses, - limits)); + operator_name, params, source, transformers, addresses, limits)); } } @@ -215,8 +213,7 @@ std::shared_ptr parse_expression(const parameter::vector &conditions std::shared_ptr parse_simplified_expression(const parameter::vector &conditions_array, address_container &addresses, const object_limits &limits) { - std::unordered_map data_ids; - return parse_expression(conditions_array, data_ids, data_source::values, {}, addresses, limits); + return parse_expression(conditions_array, data_source::values, {}, addresses, limits); } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/common/expression_parser.hpp b/src/configuration/common/expression_parser.hpp new file mode 100644 index 000000000..4c6033d41 --- /dev/null +++ b/src/configuration/common/expression_parser.hpp @@ -0,0 +1,23 @@ +// 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 "configuration/common/common.hpp" +#include "expression.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +// TODO: merge these and use default arguments +std::shared_ptr parse_expression(const parameter::vector &conditions_array, + data_source source, const std::vector &transformers, + address_container &addresses, const object_limits &limits); + +std::shared_ptr parse_simplified_expression(const parameter::vector &conditions_array, + address_container &addresses, const object_limits &limits); + +} // namespace ddwaf diff --git a/src/parser/matcher_parser.cpp b/src/configuration/common/matcher_parser.cpp similarity index 97% rename from src/parser/matcher_parser.cpp rename to src/configuration/common/matcher_parser.cpp index e1cee07b5..0d1645c49 100644 --- a/src/parser/matcher_parser.cpp +++ b/src/configuration/common/matcher_parser.cpp @@ -16,6 +16,8 @@ #include #include +#include "configuration/common/common.hpp" +#include "configuration/common/matcher_parser.hpp" // IWYU pragma: keep #include "ddwaf.h" #include "exception.hpp" #include "matcher/base.hpp" @@ -29,10 +31,8 @@ #include "matcher/phrase_match.hpp" #include "matcher/regex_match.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/matcher_parser.hpp" // IWYU pragma: keep -namespace ddwaf::parser::v2 { +namespace ddwaf { template <> std::pair> parse_matcher( @@ -205,4 +205,4 @@ std::pair> parse_matcher std::pair> parse_matcher(const parameter::map ¶ms); @@ -50,4 +50,4 @@ inline std::pair> parse_any_matcher( matcher::phrase_match, matcher::regex_match>(name, params); } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/common/reference_parser.cpp b/src/configuration/common/reference_parser.cpp new file mode 100644 index 000000000..36c08e0c9 --- /dev/null +++ b/src/configuration/common/reference_parser.cpp @@ -0,0 +1,41 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include +#include +#include + +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/reference_parser.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +reference_spec parse_reference(const parameter::map &target) +{ + auto ref_id = at(target, "rule_id", {}); + if (!ref_id.empty()) { + return {.type = reference_type::id, .ref_id = std::move(ref_id), .tags = {}}; + } + + ref_id = at(target, "id", {}); + if (!ref_id.empty()) { + return {.type = reference_type::id, .ref_id = std::move(ref_id), .tags = {}}; + } + + auto tag_map = at(target, "tags", {}); + if (!tag_map.empty()) { + std::unordered_map tags; + for (auto &[key, value] : tag_map) { tags.emplace(key, value); } + + return {.type = reference_type::tags, .ref_id = {}, .tags = std::move(tags)}; + } + + return {.type = reference_type::none, .ref_id = {}, .tags = {}}; +} + +} // namespace ddwaf diff --git a/src/configuration/common/reference_parser.hpp b/src/configuration/common/reference_parser.hpp new file mode 100644 index 000000000..33eac54e7 --- /dev/null +++ b/src/configuration/common/reference_parser.hpp @@ -0,0 +1,16 @@ +// 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 "configuration/common/configuration.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +reference_spec parse_reference(const parameter::map &target); + +} // namespace ddwaf diff --git a/src/parser/common.cpp b/src/configuration/common/transformer_parser.cpp similarity index 70% rename from src/parser/common.cpp rename to src/configuration/common/transformer_parser.cpp index 416573c67..88809f5c8 100644 --- a/src/parser/common.cpp +++ b/src/configuration/common/transformer_parser.cpp @@ -1,21 +1,21 @@ // 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. - +// This product includes software developed at Datadog +// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include #include #include #include -#include +#include +#include "condition/base.hpp" +#include "configuration/common/transformer_parser.hpp" +#include "exception.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/specification.hpp" #include "transformer/base.hpp" -namespace ddwaf::parser { +namespace ddwaf { std::optional transformer_from_string(std::string_view str) { @@ -66,27 +66,29 @@ std::optional transformer_from_string(std::string_view str) return std::nullopt; } -reference_spec parse_reference(const parameter::map &target) +std::vector parse_transformers(const parameter::vector &root, data_source &source) { - auto ref_id = at(target, "rule_id", {}); - if (!ref_id.empty()) { - return {reference_type::id, std::move(ref_id), {}}; - } - - ref_id = at(target, "id", {}); - if (!ref_id.empty()) { - return {reference_type::id, std::move(ref_id), {}}; + if (root.empty()) { + return {}; } - auto tag_map = at(target, "tags", {}); - if (!tag_map.empty()) { - std::unordered_map tags; - for (auto &[key, value] : tag_map) { tags.emplace(key, value); } + std::vector transformers; + transformers.reserve(root.size()); - return {reference_type::tags, {}, std::move(tags)}; + for (const auto &transformer_param : root) { + auto transformer = static_cast(transformer_param); + auto id = transformer_from_string(transformer); + if (id.has_value()) { + transformers.emplace_back(id.value()); + } else if (transformer == "keys_only") { + source = ddwaf::data_source::keys; + } else if (transformer == "values_only") { + source = ddwaf::data_source::values; + } else { + throw ddwaf::parsing_error("invalid transformer " + std::string(transformer)); + } } - - return {reference_type::none, {}, {}}; + return transformers; } -} // namespace ddwaf::parser +} // namespace ddwaf diff --git a/src/configuration/common/transformer_parser.hpp b/src/configuration/common/transformer_parser.hpp new file mode 100644 index 000000000..904d477af --- /dev/null +++ b/src/configuration/common/transformer_parser.hpp @@ -0,0 +1,19 @@ +// 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 "condition/base.hpp" +#include "parameter.hpp" +#include "transformer/base.hpp" + +namespace ddwaf { + +std::vector parse_transformers(const parameter::vector &root, data_source &source); + +std::optional transformer_from_string(std::string_view str); + +} // namespace ddwaf diff --git a/src/configuration/configuration_manager.cpp b/src/configuration/configuration_manager.cpp new file mode 100644 index 000000000..743f038ca --- /dev/null +++ b/src/configuration/configuration_manager.cpp @@ -0,0 +1,283 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/). Copyright 2024 Datadog, Inc. + +#include +#include +#include +#include +#include +#include + +#include "configuration/actions_parser.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/configuration_manager.hpp" +#include "configuration/data_parser.hpp" +#include "configuration/exclusion_parser.hpp" +#include "configuration/legacy_rule_parser.hpp" +#include "configuration/processor_parser.hpp" +#include "configuration/rule_override_parser.hpp" +#include "configuration/rule_parser.hpp" +#include "configuration/scanner_parser.hpp" +#include "log.hpp" +#include "parameter.hpp" +#include "ruleset_info.hpp" + +namespace ddwaf { + +void configuration_manager::load( + parameter::map &root, configuration_collector &collector, base_ruleset_info &info) +{ + auto metadata = at(root, "metadata", {}); + auto rules_version = at(metadata, "rules_version", {}); + if (!rules_version.empty()) { + info.set_ruleset_version(rules_version); + } + + auto schema_version = parse_schema_version(root); + if (schema_version == 1) { + // Legacy configurations with schema version 1 will only provide rules + DDWAF_DEBUG("Parsing legacy configuration"); + + auto it = root.find("events"); + if (it != root.end()) { + auto §ion = info.add_section("rules"); + try { + auto rules = static_cast(it->second); + if (!rules.empty()) { + parse_legacy_rules(rules, collector, section, limits_); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse rules: {}", e.what()); + section.set_error(e.what()); + } + } + + return; + } + + auto it = root.find("actions"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing actions"); + auto §ion = info.add_section("actions"); + try { + auto actions = static_cast(it->second); + if (!actions.empty()) { + parse_actions(actions, collector, section); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse actions: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("rules"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing base rules"); + auto §ion = info.add_section("rules"); + try { + auto rules = static_cast(it->second); + if (!rules.empty()) { + parse_base_rules(rules, collector, section, limits_); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse rules: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("custom_rules"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing custom rules"); + auto §ion = info.add_section("custom_rules"); + try { + auto rules = static_cast(it->second); + if (!rules.empty()) { + parse_user_rules(rules, collector, section, limits_); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse custom rules: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("rules_data"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing rule data"); + auto §ion = info.add_section("rules_data"); + try { + auto rules_data = static_cast(it->second); + if (!rules_data.empty()) { + parse_rule_data(rules_data, collector, section); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse rule data: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("rules_override"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing overrides"); + auto §ion = info.add_section("rules_override"); + try { + auto overrides = static_cast(it->second); + if (!overrides.empty()) { + parse_overrides(overrides, collector, section); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse overrides: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("exclusions"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing exclusions"); + auto §ion = info.add_section("exclusions"); + try { + auto exclusions = static_cast(it->second); + if (!exclusions.empty()) { + parse_filters(exclusions, collector, section, limits_); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse exclusions: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("exclusion_data"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing exclusion data"); + auto §ion = info.add_section("exclusion_data"); + try { + auto exclusions_data = static_cast(it->second); + if (!exclusions_data.empty()) { + parse_exclusion_data(exclusions_data, collector, section); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse exclusion data: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("processors"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing processors"); + auto §ion = info.add_section("processors"); + try { + auto processors = static_cast(it->second); + if (!processors.empty()) { + parse_processors(processors, collector, section, limits_); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse processors: {}", e.what()); + section.set_error(e.what()); + } + } + + it = root.find("scanners"); + if (it != root.end()) { + DDWAF_DEBUG("Parsing scanners"); + auto §ion = info.add_section("scanners"); + try { + auto scanners = static_cast(it->second); + if (!scanners.empty()) { + parse_scanners(scanners, collector, section); + } + } catch (const std::exception &e) { + DDWAF_WARN("Failed to parse scanners: {}", e.what()); + section.set_error(e.what()); + } + } +} + +void configuration_manager::remove_config(const configuration_change_spec &cfg) +{ + for (const auto &id : cfg.base_rules) { global_config_.base_rules.erase(id); } + for (const auto &id : cfg.user_rules) { global_config_.user_rules.erase(id); } + for (const auto &id : cfg.rule_filters) { global_config_.rule_filters.erase(id); } + for (const auto &id : cfg.input_filters) { global_config_.input_filters.erase(id); } + for (const auto &id : cfg.overrides_by_id) { global_config_.overrides_by_id.erase(id); } + for (const auto &id : cfg.overrides_by_tags) { global_config_.overrides_by_tags.erase(id); } + for (const auto &id : cfg.processors) { global_config_.processors.erase(id); } + for (const auto &id : cfg.scanners) { global_config_.scanners.erase(id); } + for (const auto &id : cfg.actions) { global_config_.actions.erase(id); } + for (const auto &[data_id, id] : cfg.rule_data) { + auto it = global_config_.rule_data.find(data_id); + if (it != global_config_.rule_data.end()) { + // Should always be true... + it->second.values.erase(id); + } + } + for (const auto &[data_id, id] : cfg.exclusion_data) { + auto it = global_config_.exclusion_data.find(data_id); + if (it != global_config_.exclusion_data.end()) { + // Should always be true... + it->second.values.erase(id); + } + } +} + +bool configuration_manager::add_or_update( + const std::string &path, parameter::map &root, base_ruleset_info &info) +{ + auto it = configs_.find(path); + if (it != configs_.end()) { + // Track the change, i.e. removed stuff + changes_ = changes_ | it->second.content; + + remove_config(it->second); + } else { + auto [new_it, res] = configs_.emplace(path, configuration_change_spec{}); + if (!res) { + return false; + } + it = new_it; + } + + configuration_change_spec new_config; + configuration_collector collector{new_config, global_config_}; + + load(root, collector, info); + if (new_config.empty()) { + configs_.erase(it); + return false; + } + + changes_ |= new_config.content; + it->second = std::move(new_config); + + return true; +} + +bool configuration_manager::remove(const std::string &path) +{ + auto it = configs_.find(path); + if (it == configs_.end()) { + return false; + } + + changes_ |= it->second.content; + + remove_config(it->second); + + configs_.erase(it); + + return true; +} + +std::pair configuration_manager::consolidate() +{ + // Copy and reset the current changes + auto current_change = changes_; + changes_ = change_set::none; + + return {global_config_, current_change}; +} + +} // namespace ddwaf diff --git a/src/configuration/configuration_manager.hpp b/src/configuration/configuration_manager.hpp new file mode 100644 index 000000000..77f5536c0 --- /dev/null +++ b/src/configuration/configuration_manager.hpp @@ -0,0 +1,44 @@ +// 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 "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" +#include "ruleset_info.hpp" + +namespace ddwaf { + +class configuration_manager { +public: + configuration_manager() = default; + ~configuration_manager() = default; + configuration_manager(configuration_manager &&) = delete; + configuration_manager(const configuration_manager &) = delete; + configuration_manager &operator=(configuration_manager &&) = delete; + configuration_manager &operator=(const configuration_manager &) = delete; + + bool add_or_update(const std::string &path, parameter::map &root, base_ruleset_info &info); + bool remove(const std::string &path); + + std::pair consolidate(); + +protected: + void remove_config(const configuration_change_spec &cfg); + + void load(parameter::map &root, configuration_collector &collector, base_ruleset_info &info); + + std::unordered_map configs_; + configuration_spec global_config_; + change_set changes_{change_set::none}; + object_limits limits_; +}; + +} // namespace ddwaf diff --git a/src/configuration/data_parser.cpp b/src/configuration/data_parser.cpp new file mode 100644 index 000000000..d24f2b0da --- /dev/null +++ b/src/configuration/data_parser.cpp @@ -0,0 +1,111 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include +#include +#include +#include +#include + +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/data_parser.hpp" +#include "exception.hpp" +#include "log.hpp" +#include "parameter.hpp" +#include "uuid.hpp" + +namespace ddwaf { + +namespace { +template std::vector parse_data(parameter &input); + +template <> std::vector parse_data(parameter &input) +{ + std::vector data; + data.reserve(input.nbEntries); + + 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)); + } + + return data; +} + +data_type data_type_from_string(std::string_view str_type) +{ + if (str_type == "ip_with_expiration") { + return data_type::ip_with_expiration; + } + + if (str_type == "data_with_expiration") { + return data_type::data_with_expiration; + } + + return data_type::unknown; +} + +void parse_data(const parameter::vector &data_array, base_section_info &info, auto &&emplace_fn) +{ + for (unsigned i = 0; i < data_array.size(); ++i) { + const ddwaf::parameter object = data_array[i]; + std::string data_id; + try { + const auto entry = static_cast(object); + + std::string id = uuidv4_generate_pseudo(); + data_id = at(entry, "id"); + + auto type_str = at(entry, "type"); + auto type = data_type_from_string(type_str); + auto data = at(entry, "data"); + if (type != data_type::data_with_expiration && type != data_type::ip_with_expiration) { + DDWAF_DEBUG("Unknown type '{}' for data id '{}'", type_str, data_id); + info.add_failed(data_id, "unknown type '" + std::string{type_str} + "'"); + continue; + } + auto values = parse_data(data); + + DDWAF_DEBUG("Parsed dynamic data '{}' of type '{}'", data_id, type_str); + info.add_loaded(data_id); + emplace_fn(std::move(data_id), std::move(id), type, std::move(values)); + } catch (const ddwaf::exception &e) { + if (data_id.empty()) { + data_id = index_to_id(i); + } + + DDWAF_ERROR("Failed to parse data id '{}': {}", data_id, e.what()); + info.add_failed(data_id, e.what()); + } + } +} + +} // namespace + +void parse_rule_data( + const parameter::vector &data_array, configuration_collector &cfg, base_section_info &info) +{ + parse_data(data_array, info, + [&cfg](std::string &&data_id, std::string &&id, data_type type, + std::vector &&data) { + cfg.emplace_rule_data(std::move(data_id), std::move(id), type, std::move(data)); + }); +} + +void parse_exclusion_data( + const parameter::vector &data_array, configuration_collector &cfg, base_section_info &info) +{ + parse_data(data_array, info, + [&cfg](std::string &&data_id, std::string &&id, data_type type, + std::vector &&data) { + cfg.emplace_exclusion_data(std::move(data_id), std::move(id), type, std::move(data)); + }); +} + +} // namespace ddwaf diff --git a/src/configuration/data_parser.hpp b/src/configuration/data_parser.hpp new file mode 100644 index 000000000..602639637 --- /dev/null +++ b/src/configuration/data_parser.hpp @@ -0,0 +1,20 @@ +// 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 "configuration/common/common.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +void parse_rule_data( + const parameter::vector &data_array, configuration_collector &cfg, base_section_info &info); +void parse_exclusion_data( + const parameter::vector &data_array, configuration_collector &cfg, base_section_info &info); + +} // namespace ddwaf diff --git a/src/parser/exclusion_parser.cpp b/src/configuration/exclusion_parser.cpp similarity index 73% rename from src/parser/exclusion_parser.cpp rename to src/configuration/exclusion_parser.cpp index c634a2858..12e394587 100644 --- a/src/parser/exclusion_parser.cpp +++ b/src/configuration/exclusion_parser.cpp @@ -12,30 +12,32 @@ #include #include "condition/base.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/common/expression_parser.hpp" +#include "configuration/common/reference_parser.hpp" +#include "configuration/exclusion_parser.hpp" #include "exception.hpp" #include "exclusion/common.hpp" #include "exclusion/object_filter.hpp" #include "log.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" -#include "parser/specification.hpp" #include "semver.hpp" #include "target_address.hpp" #include "utils.hpp" #include "version.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { namespace { -input_filter_spec parse_input_filter(const parameter::map &filter, address_container &addresses, - std::unordered_map &filter_data_ids, const object_limits &limits) +input_filter_spec parse_input_filter( + const parameter::map &filter, address_container &addresses, const object_limits &limits) { // Check for conditions first auto conditions_array = at(filter, "conditions", {}); - auto expr = parse_expression( - conditions_array, filter_data_ids, data_source::values, {}, addresses, limits); + auto expr = parse_expression(conditions_array, data_source::values, {}, addresses, limits); std::vector rules_target; auto rules_target_array = at(filter, "rules_target", {}); @@ -50,11 +52,6 @@ input_filter_spec parse_input_filter(const parameter::map &filter, address_conta auto obj_filter = std::make_shared(limits); auto inputs_array = at(filter, "inputs"); - // TODO: add empty method to object filter and check after - if (expr->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"); @@ -66,16 +63,21 @@ input_filter_spec parse_input_filter(const parameter::map &filter, address_conta obj_filter->insert(target, std::move(address), key_path); } - return {std::move(expr), std::move(obj_filter), std::move(rules_target)}; + if (expr->empty() && obj_filter->empty()) { + throw ddwaf::parsing_error("empty exclusion filter"); + } + + return {.expr = std::move(expr), + .filter = std::move(obj_filter), + .targets = std::move(rules_target)}; } -rule_filter_spec parse_rule_filter(const parameter::map &filter, address_container &addresses, - std::unordered_map &filter_data_ids, const object_limits &limits) +rule_filter_spec parse_rule_filter( + const parameter::map &filter, address_container &addresses, const object_limits &limits) { // Check for conditions first auto conditions_array = at(filter, "conditions", {}); - auto expr = parse_expression( - conditions_array, filter_data_ids, data_source::values, {}, addresses, limits); + auto expr = parse_expression(conditions_array, data_source::values, {}, addresses, limits); std::vector rules_target; auto rules_target_array = at(filter, "rules_target", {}); @@ -105,15 +107,17 @@ rule_filter_spec parse_rule_filter(const parameter::map &filter, address_contain throw ddwaf::parsing_error("empty exclusion filter"); } - return {std::move(expr), std::move(rules_target), on_match, std::move(on_match_id)}; + return {.expr = std::move(expr), + .targets = std::move(rules_target), + .on_match = on_match, + .custom_action = std::move(on_match_id)}; } } // namespace -filter_spec_container parse_filters(parameter::vector &filter_array, base_section_info &info, - std::unordered_map &filter_data_ids, const object_limits &limits) +void parse_filters(const parameter::vector &filter_array, configuration_collector &cfg, + base_section_info &info, const object_limits &limits) { - filter_spec_container filters; for (unsigned i = 0; i < filter_array.size(); i++) { const auto &node_param = filter_array[i]; auto node = static_cast(node_param); @@ -121,7 +125,7 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio try { address_container addresses; id = at(node, "id"); - if (filters.ids.find(id) != filters.ids.end()) { + if (cfg.contains_filter(id)) { DDWAF_WARN("Duplicate filter: {}", id); info.add_failed(id, "duplicate filter"); continue; @@ -138,17 +142,14 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio } if (node.find("inputs") != node.end()) { - auto filter = parse_input_filter(node, addresses, filter_data_ids, limits); - filters.ids.emplace(id); - filters.input_filters.emplace(id, std::move(filter)); + auto filter = parse_input_filter(node, addresses, limits); + cfg.emplace_filter(id, std::move(filter)); } else { - auto filter = parse_rule_filter(node, addresses, filter_data_ids, limits); - filters.ids.emplace(id); - filters.rule_filters.emplace(id, std::move(filter)); + auto filter = parse_rule_filter(node, addresses, limits); + cfg.emplace_filter(id, std::move(filter)); } DDWAF_DEBUG("Parsed exclusion filter {}", id); - - info.add_loaded(id); + info.add_loaded(std::move(id)); add_addresses_to_info(addresses, info); } catch (const unsupported_operator_version &e) { DDWAF_WARN("Skipping filter '{}': {}", id, e.what()); @@ -161,8 +162,6 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio info.add_failed(id, e.what()); } } - - return filters; } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/exclusion_parser.hpp b/src/configuration/exclusion_parser.hpp new file mode 100644 index 000000000..7705dad60 --- /dev/null +++ b/src/configuration/exclusion_parser.hpp @@ -0,0 +1,18 @@ +// 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 "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" +#include "ruleset_info.hpp" + +namespace ddwaf { + +void parse_filters(const parameter::vector &filter_array, configuration_collector &cfg, + ruleset_info::base_section_info &info, const object_limits &limits); + +} // namespace ddwaf diff --git a/src/parser/parser_v1.cpp b/src/configuration/legacy_rule_parser.cpp similarity index 54% rename from src/parser/parser_v1.cpp rename to src/configuration/legacy_rule_parser.cpp index e4c96938a..a390f9b0a 100644 --- a/src/parser/parser_v1.cpp +++ b/src/configuration/legacy_rule_parser.cpp @@ -11,12 +11,16 @@ #include #include #include -#include #include #include #include "condition/base.hpp" #include "condition/scalar_condition.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/common/transformer_parser.hpp" +#include "configuration/legacy_rule_parser.hpp" #include "ddwaf.h" #include "exception.hpp" #include "expression.hpp" @@ -27,16 +31,12 @@ #include "matcher/phrase_match.hpp" #include "matcher/regex_match.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" #include "rule.hpp" -#include "ruleset.hpp" -#include "ruleset_info.hpp" #include "target_address.hpp" #include "transformer/base.hpp" #include "utils.hpp" -namespace ddwaf::parser::v1 { +namespace ddwaf { namespace { @@ -112,8 +112,11 @@ std::shared_ptr parse_expression(parameter::vector &conditions_array key_path.emplace_back(input.substr(pos + 1, input.size())); } - def.targets.emplace_back(condition_target{root, get_target_index(root), - std::move(key_path), transformers, data_source::values}); + def.targets.emplace_back(condition_target{.name = root, + .index = get_target_index(root), + .key_path = std::move(key_path), + .transformers = transformers, + .source = data_source::values}); } conditions.emplace_back(std::make_unique( @@ -123,91 +126,74 @@ std::shared_ptr parse_expression(parameter::vector &conditions_array return std::make_shared(std::move(conditions)); } -void parse_rule(parameter::map &rule, base_section_info &info, - std::unordered_set &rule_ids, std::vector> &rules, - ddwaf::object_limits limits) -{ - auto id = at(rule, "id"); - if (rule_ids.find(id) != rule_ids.end()) { - DDWAF_WARN("duplicate rule {}", id); - info.add_failed(id, "duplicate rule"); - return; - } - - try { - std::vector rule_transformers; - auto transformers = at(rule, "transformers", parameter::vector()); - if (transformers.size() > limits.max_transformers_per_address) { - throw ddwaf::parsing_error("number of transformers beyond allowed limit"); - } +} // namespace - for (const auto &transformer_param : transformers) { - auto transformer = static_cast(transformer_param); - auto id = transformer_from_string(transformer); - if (!id.has_value()) { - throw ddwaf::parsing_error("invalid transformer" + std::string(transformer)); +void parse_legacy_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, object_limits limits) +{ + for (unsigned i = 0; i < rule_array.size(); ++i) { + std::string id; + try { + const auto &rule_param = rule_array[i]; + auto node = static_cast(rule_param); + + id = at(node, "id"); + if (cfg.contains_rule(id)) { + DDWAF_WARN("Duplicate rule {}", id); + info.add_failed(id, "duplicate rule"); + continue; } - rule_transformers.emplace_back(id.value()); - } - - auto conditions_array = at(rule, "conditions"); - auto expression = parse_expression(conditions_array, 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); + std::vector rule_transformers; + auto transformers = at(node, "transformers", parameter::vector()); + if (transformers.size() > limits.max_transformers_per_address) { + throw ddwaf::parsing_error("number of transformers beyond allowed limit"); } - } - 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(expression)); + for (const auto &transformer_param : transformers) { + auto transformer_name = static_cast(transformer_param); + auto transformer = transformer_from_string(transformer_name); + if (!transformer.has_value()) { + throw ddwaf::parsing_error( + "invalid transformer" + std::string(transformer_name)); + } + rule_transformers.emplace_back(transformer.value()); + } - rule_ids.emplace(rule_ptr->get_id()); - rules.emplace_back(rule_ptr); - info.add_loaded(rule_ptr->get_id()); - } catch (const std::exception &e) { - DDWAF_WARN("failed to parse rule '{}': {}", id, e.what()); - info.add_failed(id, e.what()); - } -} + auto conditions_array = at(node, "conditions"); + auto expression = parse_expression(conditions_array, rule_transformers, limits); -} // namespace + std::unordered_map tags; + for (auto &[key, value] : at(node, "tags")) { + try { + tags.emplace(key, std::string(value)); + } catch (const bad_cast &e) { + throw invalid_type(std::string(key), e); + } + } -void parse( - parameter::map &ruleset, base_ruleset_info &info, ddwaf::ruleset &rs, object_limits limits) -{ - auto rules_array = at(ruleset, "events"); - rs.rules.reserve(rules_array.size()); + if (tags.find("type") == tags.end()) { + throw ddwaf::parsing_error("missing key 'type'"); + } - auto §ion = info.add_section("rules"); + rule_spec spec{.enabled = true, + .source = core_rule::source_type::base, + .name = at(node, "name"), + .tags = std::move(tags), + .expr = std::move(expression), + .actions = {}}; - std::unordered_set rule_ids; - std::vector> rules; - for (unsigned i = 0; i < rules_array.size(); ++i) { - const auto &rule_param = rules_array[i]; - try { - auto rule = static_cast(rule_param); - parse_rule(rule, section, rule_ids, rules, limits); - rs.insert_rules(rules, {}); + DDWAF_DEBUG("Parsed rule {}", id); + info.add_loaded(id); + cfg.emplace_rule(std::move(id), std::move(spec)); } catch (const std::exception &e) { - DDWAF_WARN("{}", e.what()); - section.add_failed("index:" + to_string(i), e.what()); + if (id.empty()) { + id = index_to_id(i); + } + DDWAF_WARN("Failed to parse rule '{}': {}", id, e.what()); + info.add_failed(id, e.what()); } } - - if (rs.rules.empty()) { - throw ddwaf::parsing_error("no valid rules found"); - } - - DDWAF_DEBUG("Loaded %zu rules out of %zu available in the ruleset", rs.rules.size(), - rules_array.size()); } -} // namespace ddwaf::parser::v1 +} // namespace ddwaf diff --git a/src/configuration/legacy_rule_parser.hpp b/src/configuration/legacy_rule_parser.hpp new file mode 100644 index 000000000..520944e0d --- /dev/null +++ b/src/configuration/legacy_rule_parser.hpp @@ -0,0 +1,18 @@ +// 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 "configuration/common/common.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +void parse_legacy_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, object_limits limits); + +} // namespace ddwaf diff --git a/src/parser/processor_parser.cpp b/src/configuration/processor_parser.cpp similarity index 82% rename from src/parser/processor_parser.cpp rename to src/configuration/processor_parser.cpp index d76688119..bda3959a2 100644 --- a/src/parser/processor_parser.cpp +++ b/src/configuration/processor_parser.cpp @@ -10,22 +10,25 @@ #include #include -#include "builder/processor_builder.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/common/expression_parser.hpp" +#include "configuration/common/reference_parser.hpp" +#include "configuration/processor_parser.hpp" #include "exception.hpp" #include "log.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" -#include "parser/specification.hpp" #include "processor/base.hpp" #include "processor/extract_schema.hpp" #include "processor/fingerprint.hpp" +#include "ruleset_info.hpp" #include "semver.hpp" #include "target_address.hpp" #include "utils.hpp" #include "version.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { namespace { std::vector parse_processor_mappings( @@ -51,12 +54,15 @@ std::vector parse_processor_mappings( auto input_address = at(input, "address"); addresses.optional.emplace(input_address); - parameters.emplace_back(processor_parameter{ - {processor_target{get_target_index(input_address), std::move(input_address), {}}}}); + parameters.emplace_back( + processor_parameter{{processor_target{.index = get_target_index(input_address), + .name = std::move(input_address), + .key_path = {}}}}); } auto output = at(mapping, "output"); - mappings.emplace_back(processor_mapping{ - std::move(parameters), {get_target_index(output), std::move(output), {}}}); + mappings.emplace_back(processor_mapping{.inputs = std::move(parameters), + .output = { + .index = get_target_index(output), .name = std::move(output), .key_path = {}}}); } return mappings; @@ -64,12 +70,9 @@ std::vector parse_processor_mappings( } // namespace -processor_container parse_processors( - parameter::vector &processor_array, base_section_info &info, const object_limits &limits) +void parse_processors(const parameter::vector &processor_array, configuration_collector &cfg, + ruleset_info::base_section_info &info, const object_limits &limits) { - processor_container processors; - std::unordered_set known_processors; - for (unsigned i = 0; i < processor_array.size(); i++) { const auto &node_param = processor_array[i]; auto node = static_cast(node_param); @@ -78,7 +81,7 @@ processor_container parse_processors( address_container addresses; id = at(node, "id"); - if (known_processors.contains(id)) { + if (cfg.contains_processor(id)) { DDWAF_WARN("Duplicate processor: {}", id); info.add_failed(id, "duplicate processor"); continue; @@ -153,19 +156,17 @@ processor_container parse_processors( info.add_failed(id, "processor not used for evaluation or output"); continue; } + const processor_spec spec{.type = type, + .expr = std::move(expr), + .mappings = std::move(mappings), + .scanners = std::move(scanners), + .evaluate = eval, + .output = output}; DDWAF_DEBUG("Parsed processor {}", id); - known_processors.emplace(id); info.add_loaded(id); add_addresses_to_info(addresses, info); - - if (eval) { - processors.pre.emplace_back(processor_builder{type, std::move(id), std::move(expr), - std::move(mappings), std::move(scanners), eval, output}); - } else { - processors.post.emplace_back(processor_builder{type, std::move(id), std::move(expr), - std::move(mappings), std::move(scanners), eval, output}); - } + cfg.emplace_processor(std::move(id), spec); } catch (const unsupported_operator_version &e) { DDWAF_WARN("Skipping processor '{}': {}", id, e.what()); info.add_skipped(id); @@ -177,7 +178,6 @@ processor_container parse_processors( info.add_failed(id, e.what()); } } - return processors; } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/processor_parser.hpp b/src/configuration/processor_parser.hpp new file mode 100644 index 000000000..36c50c8f9 --- /dev/null +++ b/src/configuration/processor_parser.hpp @@ -0,0 +1,19 @@ +// 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 "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" +#include "ruleset_info.hpp" + +namespace ddwaf { + +void parse_processors(const parameter::vector &processor_array, configuration_collector &cfg, + ruleset_info::base_section_info &info, const object_limits &limits); + +} // namespace ddwaf diff --git a/src/parser/rule_override_parser.cpp b/src/configuration/rule_override_parser.cpp similarity index 68% rename from src/parser/rule_override_parser.cpp rename to src/configuration/rule_override_parser.cpp index ce68ac8fe..14ed4f4cd 100644 --- a/src/parser/rule_override_parser.cpp +++ b/src/configuration/rule_override_parser.cpp @@ -9,20 +9,26 @@ #include #include +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/common/reference_parser.hpp" +#include "configuration/rule_override_parser.hpp" #include "exception.hpp" #include "log.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/specification.hpp" +#include "ruleset_info.hpp" +#include "uuid.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { namespace { -std::pair parse_override(const parameter::map &node) +override_spec parse_override(const parameter::map &node) { // Note that ID is a duplicate field and will be deprecated at some point override_spec current; + current.type = reference_type::none; auto it = node.find("enabled"); if (it != node.end()) { @@ -41,17 +47,15 @@ std::pair parse_override(const parameter::map &no current.tags = std::move(tags); } - reference_type type = reference_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_reference(static_cast(target)); - if (type == reference_type::none) { - type = target_spec.type; - } else if (type != target_spec.type) { + if (current.type == reference_type::none) { + current.type = target_spec.type; + } else if (current.type != target_spec.type) { throw ddwaf::parsing_error("rule override targets rules and tags"); } @@ -59,49 +63,47 @@ std::pair parse_override(const parameter::map &no } } else { // Since the rules_target array is empty, the ID is mandatory - reference_spec ref_spec{reference_type::id, at(node, "id"), {}}; + reference_spec ref_spec{ + .type = reference_type::id, .ref_id = at(node, "id"), .tags = {}}; current.targets.emplace_back(std::move(ref_spec)); - type = reference_type::id; + current.type = reference_type::id; } if (!current.actions.has_value() && !current.enabled.has_value() && current.tags.empty()) { throw ddwaf::parsing_error("rule override without side-effects"); } - return {current, type}; + return current; } } // namespace -override_spec_container parse_overrides(parameter::vector &override_array, base_section_info &info) +void parse_overrides(const parameter::vector &override_array, configuration_collector &cfg, + ruleset_info::base_section_info &info) { - override_spec_container overrides; - for (unsigned i = 0; i < override_array.size(); ++i) { auto id = index_to_id(i); const auto &node_param = override_array[i]; auto node = static_cast(node_param); try { - auto [spec, type] = parse_override(node); - if (type == reference_type::id) { - overrides.by_ids.emplace_back(std::move(spec)); - } else if (type == reference_type::tags) { - overrides.by_tags.emplace_back(std::move(spec)); - } else { + auto spec = parse_override(node); + if (spec.type == reference_type::none) { // This code is likely unreachable DDWAF_WARN("Rule override with no targets"); info.add_failed(id, "rule override with no targets"); continue; } + DDWAF_DEBUG("Parsed override {}", id); info.add_loaded(id); + // We use a UUID since we want to have a unique identifier across + // all configurations + cfg.emplace_override(uuidv4_generate_pseudo(), std::move(spec)); } catch (const std::exception &e) { DDWAF_WARN("Failed to parse rule override: {}", e.what()); info.add_failed(id, e.what()); } } - - return overrides; } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/rule_override_parser.hpp b/src/configuration/rule_override_parser.hpp new file mode 100644 index 000000000..a9f2f0e17 --- /dev/null +++ b/src/configuration/rule_override_parser.hpp @@ -0,0 +1,18 @@ +// 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 "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" +#include "ruleset_info.hpp" + +namespace ddwaf { + +void parse_overrides(const parameter::vector &override_array, configuration_collector &cfg, + ruleset_info::base_section_info &info); + +} // namespace ddwaf diff --git a/src/parser/rule_parser.cpp b/src/configuration/rule_parser.cpp similarity index 64% rename from src/parser/rule_parser.cpp rename to src/configuration/rule_parser.cpp index 44ad6120f..2ba605f2f 100644 --- a/src/parser/rule_parser.cpp +++ b/src/configuration/rule_parser.cpp @@ -10,24 +10,26 @@ #include #include "condition/base.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/common/expression_parser.hpp" +#include "configuration/common/transformer_parser.hpp" +#include "configuration/rule_parser.hpp" #include "exception.hpp" #include "log.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" -#include "parser/specification.hpp" #include "rule.hpp" #include "semver.hpp" #include "transformer/base.hpp" #include "utils.hpp" #include "version.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { namespace { -rule_spec parse_rule(parameter::map &rule, - std::unordered_map &rule_data_ids, const object_limits &limits, +rule_spec parse_rule(parameter::map &rule, const object_limits &limits, core_rule::source_type source, address_container &addresses) { std::vector rule_transformers; @@ -40,8 +42,8 @@ rule_spec parse_rule(parameter::map &rule, rule_transformers = parse_transformers(transformers, data_source); auto conditions_array = at(rule, "conditions"); - auto expr = parse_expression( - conditions_array, rule_data_ids, data_source, rule_transformers, addresses, limits); + auto expr = + parse_expression(conditions_array, data_source, rule_transformers, addresses, limits); if (expr->empty()) { // This is likely unreachable throw ddwaf::parsing_error("rule has no valid conditions"); @@ -60,26 +62,27 @@ rule_spec parse_rule(parameter::map &rule, throw ddwaf::parsing_error("missing key 'type'"); } - return {at(rule, "enabled", true), source, at(rule, "name"), std::move(tags), - std::move(expr), at>(rule, "on_match", {})}; + return {.enabled = at(rule, "enabled", true), + .source = source, + .name = at(rule, "name"), + .tags = std::move(tags), + .expr = std::move(expr), + .actions = at>(rule, "on_match", {})}; } -} // namespace - -rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info &info, - std::unordered_map &rule_data_ids, const object_limits &limits, - core_rule::source_type source) +void parse_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, core_rule::source_type source, const object_limits &limits) { - rule_spec_container rules; for (unsigned i = 0; i < rule_array.size(); ++i) { - const auto &rule_param = rule_array[i]; - auto node = static_cast(rule_param); std::string id; try { + const auto &rule_param = rule_array[i]; + auto node = static_cast(rule_param); + address_container addresses; id = at(node, "id"); - if (rules.find(id) != rules.end()) { + if (cfg.contains_rule(id)) { DDWAF_WARN("Duplicate rule {}", id); info.add_failed(id, "duplicate rule"); continue; @@ -95,12 +98,12 @@ rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info continue; } - auto rule = parse_rule(node, rule_data_ids, limits, source, addresses); + auto rule = parse_rule(node, limits, source, addresses); + DDWAF_DEBUG("Parsed rule {}", id); info.add_loaded(id); add_addresses_to_info(addresses, info); - - rules.emplace(std::move(id), std::move(rule)); + cfg.emplace_rule(std::move(id), std::move(rule)); } catch (const unsupported_operator_version &e) { DDWAF_WARN("Skipping rule '{}': {}", id, e.what()); info.add_skipped(id); @@ -112,8 +115,19 @@ rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info info.add_failed(id, e.what()); } } +} - return rules; +} // namespace + +void parse_base_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, const object_limits &limits) +{ + parse_rules(rule_array, cfg, info, core_rule::source_type::base, limits); } -} // namespace ddwaf::parser::v2 +void parse_user_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, const object_limits &limits) +{ + parse_rules(rule_array, cfg, info, core_rule::source_type::user, limits); +} +} // namespace ddwaf diff --git a/src/configuration/rule_parser.hpp b/src/configuration/rule_parser.hpp new file mode 100644 index 000000000..61c890ea5 --- /dev/null +++ b/src/configuration/rule_parser.hpp @@ -0,0 +1,21 @@ +// 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 "configuration/common/common.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" + +namespace ddwaf { + +void parse_base_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, const object_limits &limits); + +void parse_user_rules(const parameter::vector &rule_array, configuration_collector &cfg, + base_section_info &info, const object_limits &limits); + +} // namespace ddwaf diff --git a/src/parser/scanner_parser.cpp b/src/configuration/scanner_parser.cpp similarity index 88% rename from src/parser/scanner_parser.cpp rename to src/configuration/scanner_parser.cpp index 6495e6797..5133f26a8 100644 --- a/src/parser/scanner_parser.cpp +++ b/src/configuration/scanner_parser.cpp @@ -11,19 +11,20 @@ #include #include +#include "configuration/common/common.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "configuration/common/matcher_parser.hpp" +#include "configuration/scanner_parser.hpp" #include "exception.hpp" -#include "indexer.hpp" #include "log.hpp" #include "matcher/base.hpp" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/matcher_parser.hpp" -#include "parser/parser.hpp" +#include "ruleset_info.hpp" #include "scanner.hpp" #include "semver.hpp" #include "version.hpp" -namespace ddwaf::parser::v2 { +namespace ddwaf { namespace { @@ -42,16 +43,16 @@ std::unique_ptr parse_scanner_matcher(const parameter::map &root) } // namespace -indexer parse_scanners(parameter::vector &scanner_array, base_section_info &info) +void parse_scanners(const parameter::vector &scanner_array, configuration_collector &cfg, + ruleset_info::base_section_info &info) { - indexer scanners; for (unsigned i = 0; i < scanner_array.size(); i++) { const auto &node_param = scanner_array[i]; auto node = static_cast(node_param); std::string id; try { id = at(node, "id"); - if (scanners.find_by_id(id) != nullptr) { + if (cfg.contains_scanner(id)) { DDWAF_WARN("Duplicate scanner: {}", id); info.add_failed(id, "duplicate scanner"); continue; @@ -100,8 +101,8 @@ indexer parse_scanners(parameter::vector &scanner_array, base_sec DDWAF_DEBUG("Parsed scanner {}", id); auto scnr = std::make_shared(scanner{ std::move(id), std::move(tags), std::move(key_matcher), std::move(value_matcher)}); - scanners.emplace(scnr); info.add_loaded(scnr->get_id()); + cfg.emplace_scanner(scnr); } catch (const std::exception &e) { if (id.empty()) { id = index_to_id(i); @@ -110,7 +111,6 @@ indexer parse_scanners(parameter::vector &scanner_array, base_sec info.add_failed(id, e.what()); } } - return scanners; } -} // namespace ddwaf::parser::v2 +} // namespace ddwaf diff --git a/src/configuration/scanner_parser.hpp b/src/configuration/scanner_parser.hpp new file mode 100644 index 000000000..ca22626cd --- /dev/null +++ b/src/configuration/scanner_parser.hpp @@ -0,0 +1,19 @@ +// 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 "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "parameter.hpp" +#include "ruleset_info.hpp" + +namespace ddwaf { + +void parse_scanners(const parameter::vector &scanner_array, configuration_collector &cfg, + ruleset_info::base_section_info &info); + +} // namespace ddwaf diff --git a/src/event.cpp b/src/event.cpp index 03d06fdcb..808d82cf5 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -165,9 +165,6 @@ void serialize_rule(const core_rule &rule, ddwaf_object &rule_map) for (const auto &[key, value] : rule.get_tags()) { ddwaf_object_map_addl(&tags_map, key.c_str(), key.size(), to_object(tmp, value)); } - for (const auto &[key, value] : rule.get_ancillary_tags()) { - ddwaf_object_map_addl(&tags_map, key.c_str(), key.size(), to_object(tmp, value)); - } ddwaf_object_map_add(&rule_map, "tags", &tags_map); } @@ -295,7 +292,8 @@ void event_serializer::serialize(const std::vector &events, ddwaf_result return; } - action_tracker actions{.mapper = actions_}; + action_tracker actions{ + .blocking_action = {}, .stack_id = {}, .non_blocking_actions = {}, .mapper = actions_}; ddwaf_object_array(&output.events); for (const auto &event : events) { diff --git a/src/exclusion/input_filter.cpp b/src/exclusion/input_filter.cpp index 2e3305dae..b6b125fe5 100644 --- a/src/exclusion/input_filter.cpp +++ b/src/exclusion/input_filter.cpp @@ -53,7 +53,7 @@ std::optional input_filter::match(const object_store &store, cache return std::nullopt; } - return {{rule_targets_, std::move(objects)}}; + return {{.rules = rule_targets_, .objects = std::move(objects)}}; } } // namespace ddwaf::exclusion diff --git a/src/exclusion/object_filter.hpp b/src/exclusion/object_filter.hpp index f5c616073..43c686766 100644 --- a/src/exclusion/object_filter.hpp +++ b/src/exclusion/object_filter.hpp @@ -248,6 +248,8 @@ class object_filter { targets_.emplace(target, std::move(name)); } + [[nodiscard]] bool empty() const { return target_paths_.empty(); } + object_set match( const object_store &store, cache_type &cache, bool ephemeral, ddwaf::timer &deadline) const; diff --git a/src/exclusion/rule_filter.cpp b/src/exclusion/rule_filter.cpp index 0c1dae165..db4c56cea 100644 --- a/src/exclusion/rule_filter.cpp +++ b/src/exclusion/rule_filter.cpp @@ -54,7 +54,7 @@ std::optional rule_filter::match(const object_store &store, cache_ return std::nullopt; } - return {{rule_targets_, res.ephemeral, mode_, action_}}; + return {{.rules = rule_targets_, .ephemeral = res.ephemeral, .mode = mode_, .action = action_}}; } } // namespace ddwaf::exclusion diff --git a/src/expression.cpp b/src/expression.cpp index 1f3d743e9..7ae85595e 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -24,7 +24,7 @@ eval_result expression::eval(cache_type &cache, const object_store &store, ddwaf::timer &deadline) const { if (cache.result || conditions_.empty()) { - return {true, false}; + return {.outcome = true, .ephemeral = false}; } if (cache.conditions.size() < conditions_.size()) { @@ -43,13 +43,13 @@ eval_result expression::eval(cache_type &cache, const object_store &store, auto [res, ephemeral] = cond->eval(cond_cache, store, objects_excluded, dynamic_matchers, deadline); if (!res) { - return {false, false}; + return {.outcome = false, .ephemeral = false}; } ephemeral_match = ephemeral_match || ephemeral; } cache.result = !ephemeral_match; - return {true, ephemeral_match}; + return {.outcome = true, .ephemeral = ephemeral_match}; } } // namespace ddwaf diff --git a/src/indexed_multivector.hpp b/src/indexed_multivector.hpp new file mode 100644 index 000000000..6ccc6e894 --- /dev/null +++ b/src/indexed_multivector.hpp @@ -0,0 +1,97 @@ +// 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 2025 Datadog, Inc. + +#pragma once + +#include +#include + +namespace ddwaf { + +// The indexed multivector is a container which stores multiple vectors indexed +// by a key. The main purpose of the indexed multivector is to allow the +// iteration of all vectors as a single vector, as well as the ability to remove +// individual ones as needed. Note that the vector the insertion order isn't +// preserved. +template class indexed_multivector { +public: + indexed_multivector() = default; + void emplace(Key key, std::vector element) + { + total_size_ += element.size(); + data_.emplace(std::move(key), std::move(element)); + } + void erase(const Key &key) + { + auto it = data_.find(key); + if (it != data_.end()) { + total_size_ -= it->second.size(); + data_.erase(it); + } + } + [[nodiscard]] bool empty() const { return data_.empty(); } + [[nodiscard]] std::size_t size() const { return total_size_; } + + class const_iterator { + public: + using index_iterator_type = + typename std::unordered_map>::const_iterator; + using item_iterator_type = typename std::vector::const_iterator; + const_iterator( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + index_iterator_type index_it, index_iterator_type end_index_it, + item_iterator_type item_it) + : index_it_(index_it), end_index_it_(end_index_it), item_it_(item_it) + {} + + const_iterator &operator++() + { + ++item_it_; + if (item_it_ == index_it_->second.end()) { + ++index_it_; + if (index_it_ != end_index_it_) { + item_it_ = index_it_->second.begin(); + } else { + item_it_ = {}; + } + } + return *this; + } + + bool operator==(const_iterator other) const + { + return index_it_ == other.index_it_ && item_it_ == other.item_it_; + } + + bool operator!=(const_iterator other) const { return !(*this == other); } + + const T &operator*() const { return *item_it_; } + + protected: + index_iterator_type index_it_; + index_iterator_type end_index_it_; + item_iterator_type item_it_; + }; + + const_iterator begin() const + { + if (data_.empty()) { + return end(); + } + + auto index_it = data_.begin(); + auto item_it = index_it->second.begin(); + + return {index_it, data_.end(), item_it}; + } + + const_iterator end() const { return {data_.end(), data_.end(), {}}; } + +protected: + std::unordered_map> data_; + std::size_t total_size_{0}; +}; +} // namespace ddwaf diff --git a/src/indexer.hpp b/src/indexer.hpp index 5568a58fd..1a3725b0d 100644 --- a/src/indexer.hpp +++ b/src/indexer.hpp @@ -35,6 +35,24 @@ template class indexer { return items_.erase(it); } + void erase(std::string_view id) + { + iterator it; + for (it = items_.begin(); it != items_.end(); ++it) { + if (id == (*it)->get_id()) { + break; + } + } + + if (it == items_.end()) { + return; + } + + erase(it); + } + + [[nodiscard]] bool contains(std::string_view id) const { return by_id_.contains(id); } + T *find_by_id(std::string_view id) const { auto it = by_id_.find(id); @@ -47,6 +65,7 @@ template class indexer { } [[nodiscard]] std::size_t size() const { return items_.size(); } + [[nodiscard]] bool empty() const { return items_.empty(); } void clear() { diff --git a/src/interface.cpp b/src/interface.cpp index da6bab70c..e47f2ad86 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -13,6 +13,7 @@ #include #include +#include "builder/waf_builder.hpp" #include "context.hpp" #include "ddwaf.h" #include "log.hpp" @@ -94,50 +95,31 @@ ddwaf::waf *ddwaf_init( { try { if (ruleset != nullptr) { - const ddwaf::parameter input = *ruleset; - auto free_fn = config != nullptr ? config->free_fn : ddwaf_object_free; - if (diagnostics == nullptr) { - ddwaf::null_ruleset_info ri; - return new ddwaf::waf( - input, ri, limits_from_config(config), free_fn, obfuscator_from_config(config)); - } + ddwaf::waf_builder builder( + limits_from_config(config), free_fn, obfuscator_from_config(config)); - ddwaf::ruleset_info ri; - const ddwaf::scope_exit on_exit([&]() { ri.to_object(*diagnostics); }); - - return new ddwaf::waf( - input, ri, limits_from_config(config), free_fn, obfuscator_from_config(config)); - } - } catch (const std::exception &e) { - DDWAF_ERROR("{}", e.what()); - } catch (...) { - DDWAF_ERROR("unknown exception"); - } - - return nullptr; -} - -ddwaf::waf *ddwaf_update(ddwaf::waf *handle, const ddwaf_object *ruleset, ddwaf_object *diagnostics) -{ - try { - if (handle != nullptr && ruleset != nullptr) { const ddwaf::parameter input = *ruleset; if (diagnostics == nullptr) { ddwaf::null_ruleset_info ri; - return handle->update(input, ri); + + auto input_map = static_cast(input); + builder.add_or_update("default", input_map, ri); + return new ddwaf::waf{builder.build()}; } ddwaf::ruleset_info ri; const ddwaf::scope_exit on_exit([&]() { ri.to_object(*diagnostics); }); - - return handle->update(input, ri); + auto input_map = static_cast(input); + builder.add_or_update("default", input_map, ri); + return new ddwaf::waf{builder.build()}; } } catch (const std::exception &e) { DDWAF_ERROR("{}", e.what()); } catch (...) { DDWAF_ERROR("unknown exception"); } + return nullptr; } @@ -282,4 +264,84 @@ void ddwaf_result_free(ddwaf_result *result) *result = DDWAF_RESULT_INITIALISER; } + +ddwaf_builder ddwaf_builder_init(const ddwaf_config *config) +{ + try { + auto free_fn = config != nullptr ? config->free_fn : ddwaf_object_free; + return new ddwaf::waf_builder( + limits_from_config(config), free_fn, obfuscator_from_config(config)); + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return nullptr; +} + +bool ddwaf_builder_add_or_update_config(ddwaf::waf_builder *builder, const char *path, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + uint32_t path_len, ddwaf_object *config, ddwaf_object *diagnostics) +{ + if (builder == nullptr) { + return false; + } + + try { + auto input = static_cast(*config); + auto input_map = static_cast(input); + + if (diagnostics == nullptr) { + ddwaf::null_ruleset_info ri; + return builder->add_or_update({path, path_len}, input_map, ri); + } + + ddwaf::ruleset_info ri; + const ddwaf::scope_exit on_exit([&]() { ri.to_object(*diagnostics); }); + return builder->add_or_update({path, path_len}, input_map, ri); + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return false; +} + +bool ddwaf_builder_remove_config(ddwaf::waf_builder *builder, const char *path, uint32_t path_len) +{ + if (builder == nullptr) { + return false; + } + + try { + return builder->remove({path, path_len}); + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return false; +} + +ddwaf_handle ddwaf_builder_build_instance(ddwaf::waf_builder *builder) +{ + if (builder == nullptr) { + return nullptr; + } + + try { + return new ddwaf::waf{builder->build()}; + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return nullptr; +} + +void ddwaf_builder_destroy(ddwaf_builder builder) { delete builder; } } diff --git a/src/matcher/exact_match.cpp b/src/matcher/exact_match.cpp index 8bd70c433..61c35670b 100644 --- a/src/matcher/exact_match.cpp +++ b/src/matcher/exact_match.cpp @@ -11,6 +11,7 @@ #include #include +#include "indexed_multivector.hpp" #include "matcher/exact_match.hpp" namespace ddwaf::matcher { @@ -21,11 +22,28 @@ exact_match::exact_match(std::vector &&data) : data_(std::move(data for (const auto &str : data_) { values_.emplace(str, 0); } } -exact_match::exact_match(const std::vector> &data) +exact_match::exact_match(const std::vector> &data) { data_.reserve(data.size()); values_.reserve(data.size()); - for (auto [str, expiration] : data) { + for (const auto &[str, expiration] : data) { + const auto &ref = data_.emplace_back(str); + auto res = values_.emplace(ref, expiration); + if (!res.second) { + const uint64_t prev_expiration = res.first->second; + if (prev_expiration != 0 && (expiration == 0 || expiration > prev_expiration)) { + res.first->second = expiration; + } + } + } +} + +exact_match::exact_match( + const indexed_multivector> &data) +{ + data_.reserve(data.size()); + values_.reserve(data.size()); + for (const auto &[str, expiration] : data) { const auto &ref = data_.emplace_back(str); auto res = values_.emplace(ref, expiration); if (!res.second) { diff --git a/src/matcher/exact_match.hpp b/src/matcher/exact_match.hpp index 6a5f1ffe8..e13d6a99e 100644 --- a/src/matcher/exact_match.hpp +++ b/src/matcher/exact_match.hpp @@ -10,19 +10,22 @@ #include #include +#include "indexed_multivector.hpp" #include "matcher/base.hpp" namespace ddwaf::matcher { class exact_match : public base_impl { public: - using data_type = std::vector>; + using data_type = std::vector>; static constexpr std::string_view matcher_name = "exact_match"; static constexpr std::string_view negated_matcher_name = "!exact_match"; exact_match() = default; explicit exact_match(std::vector &&data); + explicit exact_match( + const indexed_multivector> &data); explicit exact_match(const data_type &data); ~exact_match() override = default; exact_match(const exact_match &) = default; diff --git a/src/matcher/ip_match.cpp b/src/matcher/ip_match.cpp index 52b30bc4d..d0874e27c 100644 --- a/src/matcher/ip_match.cpp +++ b/src/matcher/ip_match.cpp @@ -12,6 +12,7 @@ #include #include +#include "indexed_multivector.hpp" #include "ip_utils.hpp" #include "matcher/ip_match.hpp" #include "radixlib.h" @@ -28,14 +29,34 @@ ip_match::ip_match(const std::vector &ip_list) init_tree(ip_list); } -ip_match::ip_match(const std::vector> &ip_list) +ip_match::ip_match( + const indexed_multivector> &ip_list) : rtree_(radix_new(radix_tree_bits), radix_free) { if (!rtree_) { throw std::runtime_error("failed to instantiate radix tree"); } - for (auto [str, expiration] : ip_list) { + for (const auto &[str, expiration] : ip_list) { + // Parse and populate each IP/network + ipaddr ip{}; + if (ddwaf::parse_cidr(str, ip)) { + prefix_t prefix; + // NOLINTNEXTLINE(hicpp-no-array-decay,cppcoreguidelines-pro-bounds-array-to-pointer-decay) + radix_prefix_init(FAMILY_IPv6, ip.data, ip.mask, &prefix); + radix_put_if_absent(rtree_.get(), &prefix, expiration); + } + } +} + +ip_match::ip_match(const std::vector> &ip_list) + : rtree_(radix_new(radix_tree_bits), radix_free) +{ + if (!rtree_) { + throw std::runtime_error("failed to instantiate radix tree"); + } + + for (const auto &[str, expiration] : ip_list) { // Parse and populate each IP/network ipaddr ip{}; if (ddwaf::parse_cidr(str, ip)) { diff --git a/src/matcher/ip_match.hpp b/src/matcher/ip_match.hpp index b52c80f0e..a3ee02347 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -10,6 +10,7 @@ #include #include +#include "indexed_multivector.hpp" #include "ip_utils.hpp" #include "matcher/base.hpp" @@ -17,7 +18,7 @@ namespace ddwaf::matcher { class ip_match : public base_impl { public: - using data_type = std::vector>; + using data_type = std::vector>; static constexpr std::string_view matcher_name = "ip_match"; static constexpr std::string_view negated_matcher_name = "!ip_match"; @@ -35,6 +36,8 @@ class ip_match : public base_impl { init_tree(ip_list); } + explicit ip_match( + const indexed_multivector> &ip_list); explicit ip_match(const data_type &ip_list); ~ip_match() override = default; ip_match(const ip_match &) = delete; diff --git a/src/matcher/phrase_match.cpp b/src/matcher/phrase_match.cpp index ef632e13b..e32f87c9f 100644 --- a/src/matcher/phrase_match.cpp +++ b/src/matcher/phrase_match.cpp @@ -52,12 +52,12 @@ std::pair phrase_match::match_impl(std::string_view pattern) return {false, {}}; } - auto u32_size = static_cast(pattern.size()); ac_result_t result; if (!enforce_word_boundary_) { - result = ac_match(acStructure, pattern.data(), u32_size); + result = ac_match(acStructure, pattern.data(), static_cast(pattern.size())); } else { - result = ac_match_longest_l(acStructure, pattern.data(), u32_size); + result = + ac_match_longest_l(acStructure, pattern.data(), static_cast(pattern.size())); } auto begin = static_cast(result.match_begin); diff --git a/src/parser/data_parser.cpp b/src/parser/data_parser.cpp deleted file mode 100644 index 045d6c40d..000000000 --- a/src/parser/data_parser.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2021 Datadog, Inc. - -#include -#include -#include -#include -#include -#include -#include - -#include "exception.hpp" -#include "log.hpp" -#include "matcher/base.hpp" -#include "matcher/exact_match.hpp" -#include "matcher/ip_match.hpp" -#include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/specification.hpp" - -namespace ddwaf::parser::v2 { - -namespace { -using data_with_expiration = std::vector>; - -template T parse_data(std::string_view type, parameter &input); - -template <> -data_with_expiration parse_data(std::string_view type, parameter &input) -{ - if (type != "ip_with_expiration" && type != "data_with_expiration") { - return {}; - } - - data_with_expiration data; - data.reserve(input.nbEntries); - - 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)); - } - - return data; -} - -} // namespace - -matcher_container parse_data(parameter::vector &data_array, - std::unordered_map &data_ids_to_type, base_section_info &info) -{ - matcher_container matchers; - for (unsigned i = 0; i < data_array.size(); ++i) { - const ddwaf::parameter object = data_array[i]; - std::string id; - try { - const auto entry = static_cast(object); - - id = at(entry, "id"); - - auto type = at(entry, "type"); - auto data = at(entry, "data"); - - std::string_view matcher_name; - auto it = data_ids_to_type.find(id); - if (it == data_ids_to_type.end()) { - // Infer matcher from data type - if (type == "ip_with_expiration") { - matcher_name = "ip_match"; - } else if (type == "data_with_expiration") { - matcher_name = "exact_match"; - } else { - DDWAF_DEBUG("Failed to process dynamic data id '{}", id); - info.add_failed(id, "failed to infer matcher"); - continue; - } - } else { - matcher_name = it->second; - } - - std::shared_ptr matcher; - if (matcher_name == "ip_match") { - using data_type = matcher::ip_match::data_type; - auto parsed_data = parse_data(type, data); - matcher = std::make_shared(parsed_data); - } else if (matcher_name == "exact_match") { - using data_type = matcher::exact_match::data_type; - auto parsed_data = parse_data(type, data); - matcher = std::make_shared(parsed_data); - } else { - DDWAF_WARN("Matcher {} doesn't support dynamic data", matcher_name.data()); - info.add_failed( - id, "matcher " + std::string(matcher_name) + " doesn't support dynamic data"); - continue; - } - - DDWAF_DEBUG("Parsed dynamic data {}", id); - info.add_loaded(id); - matchers.emplace(std::move(id), std::move(matcher)); - } catch (const ddwaf::exception &e) { - if (id.empty()) { - id = index_to_id(i); - } - - DDWAF_ERROR("Failed to parse data id '{}': {}", id, e.what()); - info.add_failed(id, e.what()); - } - } - - return matchers; -} - -} // namespace ddwaf::parser::v2 diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp deleted file mode 100644 index 38cc3a09f..000000000 --- a/src/parser/parser.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2021 Datadog, Inc. - -#include -#include -#include - -#include "exception.hpp" -#include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -namespace ddwaf::parser { - -unsigned parse_schema_version(parameter::map &ruleset) -{ - auto version = at(ruleset, "version"); - - 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); - - 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 deleted file mode 100644 index ac251362e..000000000 --- a/src/parser/parser.hpp +++ /dev/null @@ -1,67 +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 "builder/processor_builder.hpp" -#include "indexer.hpp" -#include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/specification.hpp" -#include "rule.hpp" -#include "ruleset.hpp" -#include "ruleset_info.hpp" - -namespace ddwaf::parser { - -unsigned parse_schema_version(parameter::map &ruleset); - -namespace v1 { -void parse( - parameter::map &ruleset, base_ruleset_info &info, ddwaf::ruleset &rs, object_limits limits); -} // namespace v1 - -namespace v2 { - -rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info &info, - std::unordered_map &rule_data_ids, const object_limits &limits, - core_rule::source_type source = core_rule::source_type::base); - -matcher_container parse_data(parameter::vector &data_array, - std::unordered_map &data_ids_to_type, base_section_info &info); - -override_spec_container parse_overrides(parameter::vector &override_array, base_section_info &info); - -filter_spec_container parse_filters(parameter::vector &filter_array, base_section_info &info, - std::unordered_map &filter_data_ids, const object_limits &limits); - -processor_container parse_processors( - parameter::vector &processor_array, base_section_info &info, const object_limits &limits); - -indexer parse_scanners(parameter::vector &scanner_array, base_section_info &info); - -std::shared_ptr parse_actions( - parameter::vector &actions_array, base_section_info &info); - -std::shared_ptr parse_expression(const parameter::vector &conditions_array, - std::unordered_map &data_ids_to_type, data_source source, - const std::vector &transformers, address_container &addresses, - const object_limits &limits); - -std::shared_ptr parse_simplified_expression(const parameter::vector &conditions_array, - address_container &addresses, const object_limits &limits); - -std::vector parse_transformers(const parameter::vector &root, data_source &source); - -// std::pair> parse_matcher( -// std::string_view name, const parameter::map ¶ms); - -} // namespace v2 -} // namespace ddwaf::parser diff --git a/src/parser/specification.hpp b/src/parser/specification.hpp deleted file mode 100644 index d741b4fc5..000000000 --- a/src/parser/specification.hpp +++ /dev/null @@ -1,91 +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 "exception.hpp" -#include "exclusion/object_filter.hpp" -#include "exclusion/rule_filter.hpp" -#include "expression.hpp" -#include "parameter.hpp" -#include "processor/base.hpp" -#include "rule.hpp" -#include "scanner.hpp" - -namespace ddwaf::parser { - -struct rule_spec { - bool enabled; - core_rule::source_type source; - std::string name; - std::unordered_map tags; - std::shared_ptr expr; - std::vector actions; -}; - -enum class reference_type { none, id, tags }; - -struct reference_spec { - reference_type type; - std::string ref_id; - std::unordered_map tags; -}; - -struct override_spec { - std::optional enabled; - std::optional> actions; - std::vector targets; - std::unordered_map tags; -}; - -struct rule_filter_spec { - std::shared_ptr expr; - std::vector targets; - exclusion::filter_mode on_match; - std::string custom_action; -}; - -struct input_filter_spec { - std::shared_ptr expr; - std::shared_ptr filter; - std::vector targets; -}; - -// Containers -using rule_spec_container = std::unordered_map; -using matcher_container = std::unordered_map>; -using scanner_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/parser/transformer_parser.cpp b/src/parser/transformer_parser.cpp deleted file mode 100644 index 5d240f941..000000000 --- a/src/parser/transformer_parser.cpp +++ /dev/null @@ -1,48 +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 2024 Datadog, Inc. - -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2021 Datadog, Inc. -#include -#include -#include - -#include "condition/base.hpp" -#include "exception.hpp" -#include "parameter.hpp" -#include "parser/common.hpp" -#include "transformer/base.hpp" - -namespace ddwaf::parser::v2 { - -std::vector parse_transformers(const parameter::vector &root, data_source &source) -{ - if (root.empty()) { - return {}; - } - - std::vector transformers; - transformers.reserve(root.size()); - - for (const auto &transformer_param : root) { - auto transformer = static_cast(transformer_param); - auto id = transformer_from_string(transformer); - if (id.has_value()) { - transformers.emplace_back(id.value()); - } else if (transformer == "keys_only") { - source = ddwaf::data_source::keys; - } else if (transformer == "values_only") { - source = ddwaf::data_source::values; - } else { - throw ddwaf::parsing_error("invalid transformer " + std::string(transformer)); - } - } - return transformers; -} - -} // namespace ddwaf::parser::v2 diff --git a/src/processor/extract_schema.cpp b/src/processor/extract_schema.cpp index f1a9dbe65..f9366b793 100644 --- a/src/processor/extract_schema.cpp +++ b/src/processor/extract_schema.cpp @@ -39,6 +39,7 @@ enum class scalar_type : uint8_t { null = 1, boolean = 2, integer = 4, string = struct node_scalar { scalar_type type{scalar_type::null}; + // NOLINTNEXTLINE(readability-redundant-member-init) std::unordered_map tags{}; mutable std::size_t hash{0}; }; @@ -268,6 +269,7 @@ ddwaf_object node_serialize::operator()(const node_record_ptr &node) const return array; } +namespace { // NOLINTNEXTLINE(misc-no-recursion) ddwaf_object serialize(const base_node &node) { return std::visit(node_serialize{}, node); } @@ -281,21 +283,21 @@ base_node generate_helper(const ddwaf_object *object, std::string_view key, switch (object->type) { case DDWAF_OBJ_NULL: - return node_scalar{scalar_type::null}; + return node_scalar{.type = scalar_type::null}; case DDWAF_OBJ_FLOAT: - return node_scalar{scalar_type::real}; + return node_scalar{.type = scalar_type::real}; case DDWAF_OBJ_BOOL: - return node_scalar{scalar_type::boolean}; + return node_scalar{.type = scalar_type::boolean}; case DDWAF_OBJ_STRING: for (const auto *scanner : scanners) { if (scanner->eval(key, *object)) { - return node_scalar{scalar_type::string, scanner->get_tags()}; + return node_scalar{.type = scalar_type::string, .tags = scanner->get_tags()}; } } - return node_scalar{scalar_type::string}; + return node_scalar{.type = scalar_type::string}; case DDWAF_OBJ_SIGNED: case DDWAF_OBJ_UNSIGNED: - return node_scalar{scalar_type::integer}; + return node_scalar{.type = scalar_type::integer}; case DDWAF_OBJ_MAP: { auto length = static_cast(object->nbEntries); node_record_ptr record = std::make_unique(); @@ -347,6 +349,8 @@ ddwaf_object generate( generate_helper(object, {}, scanners, extract_schema::max_container_depth, deadline)); } +} // namespace + } // namespace schema std::pair extract_schema::eval_impl( diff --git a/src/processor/fingerprint.cpp b/src/processor/fingerprint.cpp index 61110f1ec..a50a459b0 100644 --- a/src/processor/fingerprint.cpp +++ b/src/processor/fingerprint.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -168,10 +169,8 @@ void normalize_value(std::string_view key, std::string &buffer, bool trailing_se template struct field_generator { using output_type = Output; - field_generator() = default; +public: ~field_generator() = default; - field_generator(const field_generator &) = default; - field_generator(field_generator &&) noexcept = default; field_generator &operator=(const field_generator &) = default; field_generator &operator=(field_generator &&) noexcept = default; @@ -185,6 +184,12 @@ template struct field_generato } } output_type operator()() { return static_cast(this)->generate(); } + +private: + field_generator(const field_generator &) = default; + field_generator() = default; + field_generator(field_generator &&) noexcept = default; + friend Derived; }; struct string_field : field_generator { @@ -254,9 +259,7 @@ struct key_hash_field : field_generator { const std::string_view key{ child.parameterName, static_cast(child.parameterNameLength)}; - if (max_string_size > key.size()) { - max_string_size = key.size(); - } + max_string_size = std::max(max_string_size, key.size()); keys.emplace_back(key); } @@ -334,9 +337,7 @@ struct kv_hash_fields : field_generator &get_tags() const { return tags_; } - const std::unordered_map &get_ancillary_tags() const - { - return ancillary_tags_; - } - - void set_ancillary_tag(const std::string &key, const std::string &value) - { - // Ancillary tags aren't allowed to overlap with standard tags - if (!tags_.contains(key)) { - ancillary_tags_[key] = value; - } - } [[nodiscard]] bool has_actions() const { return !actions_.empty(); } const std::vector &get_actions() const { return actions_; } - void set_actions(std::vector new_actions) { actions_ = std::move(new_actions); } - void set_verdict(verdict_type verdict) { verdict_ = verdict; } verdict_type get_verdict() const { return verdict_; } void get_addresses(std::unordered_map &addresses) const { @@ -137,9 +123,6 @@ class core_rule { std::string_view type_; rule_module_category mod_; - // Tags provided through rules_override - std::unordered_map ancillary_tags_; - // Evaluable expression encompassing all the rule's conditions std::shared_ptr expr_; }; diff --git a/src/ruleset.hpp b/src/ruleset.hpp index b5dda5253..f8d0ef272 100644 --- a/src/ruleset.hpp +++ b/src/ruleset.hpp @@ -159,7 +159,7 @@ struct ruleset { std::unordered_map> exclusion_matchers; std::vector> scanners; - std::shared_ptr actions; + std::shared_ptr actions; // Rule modules std::array rule_modules; diff --git a/src/ruleset_builder.cpp b/src/ruleset_builder.cpp deleted file mode 100644 index c2239a9a8..000000000 --- a/src/ruleset_builder.cpp +++ /dev/null @@ -1,473 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2021 Datadog, Inc. -// -#include -#include -#include -#include -#include -#include -#include -#include - -#include "action_mapper.hpp" -#include "exception.hpp" -#include "exclusion/input_filter.hpp" -#include "exclusion/rule_filter.hpp" -#include "indexer.hpp" -#include "log.hpp" -#include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" -#include "parser/specification.hpp" -#include "rule.hpp" -#include "ruleset.hpp" -#include "ruleset_builder.hpp" -#include "ruleset_info.hpp" - -namespace ddwaf { - -constexpr ruleset_builder::change_state operator|( - ruleset_builder::change_state lhs, ruleset_builder::change_state rhs) -{ - return static_cast( - static_cast>(lhs) | - static_cast>(rhs)); -} - -constexpr ruleset_builder::change_state operator&( - ruleset_builder::change_state lhs, ruleset_builder::change_state rhs) -{ - return static_cast( - static_cast>(lhs) & - static_cast>(rhs)); -} - -namespace { - -std::set references_to_rules( - const std::vector &references, const indexer &rules) -{ - std::set rule_refs; - if (!references.empty()) { - for (const auto &ref : references) { - if (ref.type == parser::reference_type::id) { - auto *rule = rules.find_by_id(ref.ref_id); - if (rule == nullptr) { - continue; - } - rule_refs.emplace(rule); - } else if (ref.type == parser::reference_type::tags) { - auto current_refs = rules.find_by_tags(ref.tags); - rule_refs.merge(current_refs); - } - } - } else { - // An empty rules reference applies to all rules - for (const auto &rule : rules) { rule_refs.emplace(rule.get()); } - } - return rule_refs; -} - -core_rule::verdict_type obtain_rule_verdict( - const action_mapper &mapper, const std::vector &rule_actions) -{ - for (const auto &action : rule_actions) { - auto it = mapper.find(action); - if (it == mapper.end()) { - continue; - } - - auto action_mode = it->second.type; - if (is_blocking_action(action_mode)) { - return core_rule::verdict_type::block; - } - } - return core_rule::verdict_type::monitor; -} - -} // namespace - -std::shared_ptr ruleset_builder::build(parameter::map &root, base_ruleset_info &info) -{ - // Load new rules, overrides and exclusions - auto state = load(root, info); - - if (state == change_state::none) { - return {}; - } - - constexpr static change_state base_rule_update = - change_state::rules | change_state::overrides | change_state::actions; - constexpr static change_state custom_rule_update = - change_state::custom_rules | change_state::actions; - constexpr static change_state filters_update = - base_rule_update | custom_rule_update | change_state::filters; - constexpr static change_state processors_update = - change_state::processors | change_state::scanners; - // When a configuration with 'rules' or 'rules_override' is received, we - // 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 & base_rule_update) != change_state::none) { - final_base_rules_.clear(); - - // Initially, new rules are generated from their spec - for (const auto &[id, spec] : base_rules_) { - auto rule_ptr = std::make_shared( - id, spec.name, spec.tags, spec.expr, spec.actions, spec.enabled, spec.source); - final_base_rules_.emplace(rule_ptr); - } - - // Overrides only impact base rules since user rules can already be modified by the user - for (const auto &ovrd : overrides_.by_tags) { - auto rule_targets = references_to_rules(ovrd.targets, final_base_rules_); - for (const auto &rule_ptr : rule_targets) { - if (ovrd.enabled.has_value()) { - rule_ptr->toggle(*ovrd.enabled); - } - - if (ovrd.actions.has_value()) { - rule_ptr->set_actions(*ovrd.actions); - } - - for (const auto &[tag, value] : ovrd.tags) { - rule_ptr->set_ancillary_tag(tag, value); - } - } - } - - for (const auto &ovrd : overrides_.by_ids) { - auto rule_targets = references_to_rules(ovrd.targets, final_base_rules_); - for (const auto &rule_ptr : rule_targets) { - if (ovrd.enabled.has_value()) { - rule_ptr->toggle(*ovrd.enabled); - } - - if (ovrd.actions.has_value()) { - rule_ptr->set_actions(*ovrd.actions); - } - - for (const auto &[tag, value] : ovrd.tags) { - rule_ptr->set_ancillary_tag(tag, value); - } - } - } - - // Update blocking mode and remove any disabled rules - for (auto it = final_base_rules_.begin(); it != final_base_rules_.end();) { - if (!(*it)->is_enabled()) { - it = final_base_rules_.erase(it); - continue; - } - - auto mode = obtain_rule_verdict(*actions_, (*it)->get_actions()); - (*it)->set_verdict(mode); - - ++it; - } - } - - if ((state & custom_rule_update) != change_state::none) { - final_user_rules_.clear(); - // Initially, new rules are generated from their spec - for (const auto &[id, spec] : user_rules_) { - auto mode = obtain_rule_verdict(*actions_, spec.actions); - auto rule_ptr = std::make_shared( - id, spec.name, spec.tags, spec.expr, spec.actions, spec.enabled, spec.source, mode); - if (!rule_ptr->is_enabled()) { - // Skip disabled rules - continue; - } - final_user_rules_.emplace(rule_ptr); - } - } - - // Generate exclusion filters targetting all final rules - if ((state & filters_update) != change_state::none) { - rule_filters_.clear(); - input_filters_.clear(); - - // First generate rule filters - for (const auto &[id, filter] : exclusions_.rule_filters) { - auto rule_targets = references_to_rules(filter.targets, final_base_rules_); - rule_targets.merge(references_to_rules(filter.targets, final_user_rules_)); - - auto filter_ptr = std::make_shared( - id, filter.expr, std::move(rule_targets), filter.on_match, filter.custom_action); - rule_filters_.emplace(filter_ptr->get_id(), filter_ptr); - } - - // Finally input filters - for (auto &[id, filter] : exclusions_.input_filters) { - auto rule_targets = references_to_rules(filter.targets, final_base_rules_); - rule_targets.merge(references_to_rules(filter.targets, final_user_rules_)); - - auto filter_ptr = std::make_shared( - id, filter.expr, std::move(rule_targets), filter.filter); - input_filters_.emplace(filter_ptr->get_id(), filter_ptr); - } - } - - // Generate new processors - if ((state & processors_update) != change_state::none) { - preprocessors_.clear(); - postprocessors_.clear(); - - for (auto &builder : processors_.pre) { - auto proc = builder.build(scanners_); - preprocessors_.emplace(proc->get_id(), std::move(proc)); - } - - for (auto &builder : processors_.post) { - auto proc = builder.build(scanners_); - postprocessors_.emplace(proc->get_id(), std::move(proc)); - } - } - - auto rs = std::make_shared(); - rs->insert_rules(final_base_rules_.items(), final_user_rules_.items()); - rs->insert_filters(rule_filters_); - rs->insert_filters(input_filters_); - rs->insert_preprocessors(preprocessors_); - rs->insert_postprocessors(postprocessors_); - rs->rule_matchers = rule_matchers_; - rs->exclusion_matchers = exclusion_matchers_; - rs->scanners = scanners_.items(); - rs->actions = actions_; - rs->free_fn = free_fn_; - rs->event_obfuscator = event_obfuscator_; - - // Since disabled rules aren't added to the final ruleset, we must check - // again that there are rules available. - if (rs->rules.empty()) { - DDWAF_WARN("No valid rules found"); - throw parsing_error("no valid or enabled rules found"); - } - - return rs; -} - -ruleset_builder::change_state ruleset_builder::load(parameter::map &root, base_ruleset_info &info) -{ - change_state state = change_state::none; - - auto metadata = parser::at(root, "metadata", {}); - auto rules_version = parser::at(metadata, "rules_version", {}); - if (!rules_version.empty()) { - info.set_ruleset_version(rules_version); - } - - auto it = root.find("actions"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing actions"); - auto §ion = info.add_section("actions"); - try { - // If the actions array is empty, an empty action mapper will be - // generated. Note that this mapper will still contain the default - // actions. - auto actions = static_cast(it->second); - actions_ = parser::v2::parse_actions(actions, section); - state = state | change_state::actions; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse actions: {}", e.what()); - section.set_error(e.what()); - } - } - - if (!actions_) { - // Ensure that the actions mapper is never invalid - state = state | change_state::actions; - actions_ = action_mapper_builder().build_shared(); - } - - it = root.find("rules"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing base rules"); - auto §ion = info.add_section("rules"); - try { - auto rules = static_cast(it->second); - rule_data_ids_.clear(); - - if (!rules.empty()) { - base_rules_ = parser::v2::parse_rules(rules, section, rule_data_ids_, limits_); - } else { - DDWAF_DEBUG("Clearing all base rules"); - base_rules_.clear(); - } - state = state | change_state::rules; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse rules: {}", e.what()); - section.set_error(e.what()); - } - } - - it = root.find("custom_rules"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing custom rules"); - auto §ion = info.add_section("custom_rules"); - try { - auto rules = static_cast(it->second); - if (!rules.empty()) { - // Rule data is currently not supported by custom rules so these will - // be discarded after - decltype(rule_data_ids_) rule_data_ids; - - auto new_user_rules = parser::v2::parse_rules( - rules, section, rule_data_ids, limits_, core_rule::source_type::user); - user_rules_ = std::move(new_user_rules); - } else { - DDWAF_DEBUG("Clearing all custom rules"); - user_rules_.clear(); - } - state = state | change_state::custom_rules; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse custom rules: {}", e.what()); - section.set_error(e.what()); - } - } - - if (base_rules_.empty() && user_rules_.empty()) { - // If we haven't received rules and our base ruleset is empty, the - // WAF can't proceed. - DDWAF_WARN("No valid rules found"); - throw parsing_error("no valid rules found"); - } - - it = root.find("rules_data"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing rule data"); - auto §ion = info.add_section("rules_data"); - try { - auto rules_data = static_cast(it->second); - if (!rules_data.empty()) { - auto new_matchers = parser::v2::parse_data(rules_data, rule_data_ids_, section); - if (new_matchers.empty()) { - // The rules_data array might have unrelated IDs, so we need - // to consider "no valid IDs" as an empty rules_data - rule_matchers_.clear(); - } else { - rule_matchers_ = std::move(new_matchers); - } - } else { - DDWAF_DEBUG("Clearing all rule data"); - rule_matchers_.clear(); - } - state = state | change_state::rule_data; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse rule data: {}", e.what()); - section.set_error(e.what()); - } - } - - it = root.find("rules_override"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing overrides"); - auto §ion = info.add_section("rules_override"); - try { - auto overrides = static_cast(it->second); - if (!overrides.empty()) { - overrides_ = parser::v2::parse_overrides(overrides, section); - } else { - DDWAF_DEBUG("Clearing all overrides"); - overrides_.clear(); - } - state = state | change_state::overrides; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse overrides: {}", e.what()); - section.set_error(e.what()); - } - } - - it = root.find("exclusions"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing exclusions"); - auto §ion = info.add_section("exclusions"); - try { - auto exclusions = static_cast(it->second); - filter_data_ids_.clear(); - if (!exclusions.empty()) { - exclusions_ = - parser::v2::parse_filters(exclusions, section, filter_data_ids_, limits_); - } else { - DDWAF_DEBUG("Clearing all exclusions"); - exclusions_.clear(); - } - state = state | change_state::filters; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse exclusions: {}", e.what()); - section.set_error(e.what()); - } - } - - it = root.find("exclusion_data"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing exclusion data"); - auto §ion = info.add_section("exclusions_data"); - try { - auto exclusions_data = static_cast(it->second); - if (!exclusions_data.empty()) { - auto new_matchers = - parser::v2::parse_data(exclusions_data, filter_data_ids_, section); - if (new_matchers.empty()) { - // The exclusions_data array might have unrelated IDs, so we need - // to consider "no valid IDs" as an empty exclusions_data - exclusion_matchers_.clear(); - } else { - exclusion_matchers_ = std::move(new_matchers); - } - } else { - DDWAF_DEBUG("Clearing all exclusion data"); - exclusion_matchers_.clear(); - } - state = state | change_state::exclusion_data; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse exclusion data: {}", e.what()); - section.set_error(e.what()); - } - } - - it = root.find("processors"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing processors"); - auto §ion = info.add_section("processors"); - try { - auto processors = static_cast(it->second); - if (!processors.empty()) { - processors_ = parser::v2::parse_processors(processors, section, limits_); - } else { - DDWAF_DEBUG("Clearing all processors"); - processors_.clear(); - } - state = state | change_state::processors; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse processors: {}", e.what()); - section.set_error(e.what()); - } - } - - it = root.find("scanners"); - if (it != root.end()) { - DDWAF_DEBUG("Parsing scanners"); - auto §ion = info.add_section("scanners"); - try { - auto scanners = static_cast(it->second); - if (!scanners.empty()) { - scanners_ = parser::v2::parse_scanners(scanners, section); - } else { - DDWAF_DEBUG("Clearing all scanners"); - scanners_.clear(); - } - state = state | change_state::scanners; - } catch (const std::exception &e) { - DDWAF_WARN("Failed to parse scanners: {}", e.what()); - section.set_error(e.what()); - } - } - - return state; -} - -} // namespace ddwaf diff --git a/src/scanner.hpp b/src/scanner.hpp index 35f7536e0..01bd9cfd1 100644 --- a/src/scanner.hpp +++ b/src/scanner.hpp @@ -49,6 +49,7 @@ class scanner { const std::unordered_map &get_tags() const { return tags_; } std::string_view get_id() const { return id_; } + const std::string &get_id_ref() const { return id_; } protected: static bool eval_matcher(const std::unique_ptr &matcher, const ddwaf_object &obj) diff --git a/src/tokenizer/shell.cpp b/src/tokenizer/shell.cpp index acadcdb19..925dd70b5 100644 --- a/src/tokenizer/shell.cpp +++ b/src/tokenizer/shell.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. #include +#include #include #include #include @@ -72,7 +73,7 @@ void find_executables_and_strip_whitespaces(std::vector &tokens) // The scope within the command, this helps identify high level constructs // which end up evaluating as part of a command, e.g. an executable // generated from a command substitution - enum class command_scope { + enum class command_scope : uint8_t { variable_definition_or_executable, variable_definition, arguments, diff --git a/src/transformer/common/utf8.cpp b/src/transformer/common/utf8.cpp index 318fa2803..15ffe5866 100644 --- a/src/transformer/common/utf8.cpp +++ b/src/transformer/common/utf8.cpp @@ -18,6 +18,68 @@ extern "C" { namespace ddwaf::utf8 { +namespace { + +int8_t findNextGlyphLength(const char *utf8Buffer, uint64_t lengthLeft) +{ + if (lengthLeft == 0) { + return 0; + } + + // We're going to assume the caller provided us with the beginning of a UTF-8 sequence. + + // Valid UTF8 has a specific binary format. + // If it's a single byte UTF8 character, then it is always of form '0xxxxxxx', where 'x' is any + // binary digit. If it's a two byte UTF8 character, then it's always of form '110xxxxx + // 10xxxxxx'. Similarly for three and four byte UTF8 characters it starts with '1110xxxx' and + // '11110xxx' followed + // by '10xxxxxx' one less times as there are bytes. + + const auto firstByte = (uint8_t)utf8Buffer[0]; + int8_t expectedSequenceLength = -1; + + // Looking for 0xxxxxxx + // If the highest bit is 0, we know it's a single byte sequence. + if ((firstByte & 0x80) == 0) { + return 1; + } + + // Looking for 110xxxxx + // Signify a sequence of 2 bytes + if ((firstByte >> 5) == 0x6) { + expectedSequenceLength = 2; + } + + // Looking for 1110xxxx + // Signify a sequence of 3 bytes + else if ((firstByte >> 4) == 0xe) { + expectedSequenceLength = 3; + } + + // Looking for 11110xxx + // Signify a sequence of 4 bytes + else if ((firstByte >> 3) == 0x1e) { + expectedSequenceLength = 4; + } + + // If we found a valid prefix, we check that it makes sense based on the length left + if (expectedSequenceLength < 0 || ((uint64_t)expectedSequenceLength) > lengthLeft) { + return -1; + } + + // If it's plausible, we then check if the bytes are valid + for (int8_t i = 1; i < expectedSequenceLength; ++i) { + // Every byte in the sequence must be prefixed by 10xxxxxx + if ((((uint8_t)utf8Buffer[i]) >> 6) != 0x2) { + return -1; + } + } + + return expectedSequenceLength; +} + +} // namespace + uint8_t codepoint_to_bytes(uint32_t codepoint, char *utf8_buffer) { // Handle the easy case of ASCII @@ -90,64 +152,6 @@ uint8_t write_codepoint(uint32_t codepoint, char *utf8Buffer, uint64_t lengthLef return codepoint_to_bytes(codepoint, utf8Buffer); } -int8_t findNextGlyphLength(const char *utf8Buffer, uint64_t lengthLeft) -{ - if (lengthLeft == 0) { - return 0; - } - - // We're going to assume the caller provided us with the beginning of a UTF-8 sequence. - - // Valid UTF8 has a specific binary format. - // If it's a single byte UTF8 character, then it is always of form '0xxxxxxx', where 'x' is any - // binary digit. If it's a two byte UTF8 character, then it's always of form '110xxxxx - // 10xxxxxx'. Similarly for three and four byte UTF8 characters it starts with '1110xxxx' and - // '11110xxx' followed - // by '10xxxxxx' one less times as there are bytes. - - const auto firstByte = (uint8_t)utf8Buffer[0]; - int8_t expectedSequenceLength = -1; - - // Looking for 0xxxxxxx - // If the highest bit is 0, we know it's a single byte sequence. - if ((firstByte & 0x80) == 0) { - return 1; - } - - // Looking for 110xxxxx - // Signify a sequence of 2 bytes - if ((firstByte >> 5) == 0x6) { - expectedSequenceLength = 2; - } - - // Looking for 1110xxxx - // Signify a sequence of 3 bytes - else if ((firstByte >> 4) == 0xe) { - expectedSequenceLength = 3; - } - - // Looking for 11110xxx - // Signify a sequence of 4 bytes - else if ((firstByte >> 3) == 0x1e) { - expectedSequenceLength = 4; - } - - // If we found a valid prefix, we check that it makes sense based on the length left - if (expectedSequenceLength < 0 || ((uint64_t)expectedSequenceLength) > lengthLeft) { - return -1; - } - - // If it's plausible, we then check if the bytes are valid - for (int8_t i = 1; i < expectedSequenceLength; ++i) { - // Every byte in the sequence must be prefixed by 10xxxxxx - if ((((uint8_t)utf8Buffer[i]) >> 6) != 0x2) { - return -1; - } - } - - return expectedSequenceLength; -} - uint32_t fetch_next_codepoint(const char *utf8Buffer, uint64_t &position, uint64_t length) { if (position > length) { @@ -241,6 +245,7 @@ size_t normalize_codepoint(uint32_t codepoint, int32_t *wbBuffer, size_t wbBuffe const auto decomposedLength = (size_t)utf8proc_decompose_char((int32_t)codepoint, wbBuffer, (utf8proc_ssize_t)wbBufferLength, + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) (utf8proc_option_t)(UTF8PROC_DECOMPOSE | UTF8PROC_IGNORE | UTF8PROC_COMPAT | UTF8PROC_LUMP | UTF8PROC_STRIPMARK | UTF8PROC_STRIPNA | UTF8PROC_CASEFOLD), nullptr); diff --git a/src/transformer/compress_whitespace.cpp b/src/transformer/compress_whitespace.cpp index 527514a4b..baa811223 100644 --- a/src/transformer/compress_whitespace.cpp +++ b/src/transformer/compress_whitespace.cpp @@ -30,6 +30,7 @@ bool compress_whitespace::transform_impl(cow_string &str) // We check with read - 1 to make sure at least one space is commited if (str.at(read) == ' ' && str.at(read - 1) == ' ') { // Skip ahead + // NOLINTNEXTLINE(bugprone-inc-dec-in-conditions) while (++read < str.length() && str.at(read) == ' ') {} // Should we run the end of the loop? diff --git a/src/transformer/manager.cpp b/src/transformer/manager.cpp index ed9e92227..3eac8b6f4 100644 --- a/src/transformer/manager.cpp +++ b/src/transformer/manager.cpp @@ -29,6 +29,8 @@ namespace ddwaf::transformer { +namespace { + bool call_transformer(transformer_id id, cow_string &str) { switch (id) { @@ -73,6 +75,8 @@ bool call_transformer(transformer_id id, cow_string &str) return false; } +} // namespace + bool manager::transform(const ddwaf_object &source, ddwaf_object &destination, const std::span &transformers) { diff --git a/src/transformer/remove_comments.cpp b/src/transformer/remove_comments.cpp index 36c2d33d4..e54644f89 100644 --- a/src/transformer/remove_comments.cpp +++ b/src/transformer/remove_comments.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. #include +#include #include #include "transformer/common/cow_string.hpp" @@ -13,7 +14,7 @@ namespace ddwaf::transformer { bool remove_comments::transform_impl(cow_string &str) { - enum class comment_type { unknown, html, c, eol } type = comment_type::unknown; + enum class comment_type : uint8_t { unknown, html, c, eol } type = comment_type::unknown; bool comment_found = false; diff --git a/src/uri_utils.cpp b/src/uri_utils.cpp index 3f68045be..824135062 100644 --- a/src/uri_utils.cpp +++ b/src/uri_utils.cpp @@ -66,7 +66,7 @@ namespace ddwaf { namespace { -enum class token_type { +enum class token_type : uint8_t { none, scheme, scheme_authority_or_path, diff --git a/src/utils.cpp b/src/utils.cpp index 354c630ff..d1ca612ca 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -13,6 +13,8 @@ namespace ddwaf::object { +namespace { + void clone_helper(const ddwaf_object &source, ddwaf_object &destination) { switch (source.type) { @@ -46,6 +48,8 @@ void clone_helper(const ddwaf_object &source, ddwaf_object &destination) } } +} // namespace + ddwaf_object clone(ddwaf_object *input) { ddwaf_object tmp; diff --git a/src/waf.cpp b/src/waf.cpp deleted file mode 100644 index a66afd686..000000000 --- a/src/waf.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2021 Datadog, Inc. -#include -#include -#include -#include - -#include "action_mapper.hpp" -#include "ddwaf.h" -#include "exception.hpp" -#include "log.hpp" -#include "obfuscator.hpp" -#include "parameter.hpp" -#include "parser/parser.hpp" -#include "ruleset.hpp" -#include "ruleset_builder.hpp" -#include "ruleset_info.hpp" -#include "utils.hpp" -#include "waf.hpp" - -namespace ddwaf { - -waf::waf(ddwaf::parameter input, ddwaf::base_ruleset_info &info, ddwaf::object_limits limits, - ddwaf_object_free_fn free_fn, std::shared_ptr event_obfuscator) -{ - auto input_map = static_cast(input); - - unsigned version = 2; - - 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): {}", e.what()); - } - } - - // 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; - rs.actions = std::make_shared(); - DDWAF_DEBUG("Parsing ruleset with schema version 1.x"); - parser::v1::parse(input_map, info, rs, limits); - ruleset_ = std::make_shared(std::move(rs)); - return; - } - - if (version == 2) { - DDWAF_DEBUG("Parsing ruleset with schema version 2.x"); - builder_ = std::make_shared(limits, free_fn, std::move(event_obfuscator)); - ruleset_ = builder_->build(input, info); - if (!ruleset_) { - throw std::runtime_error("failed to instantiate WAF"); - } - return; - } - - DDWAF_ERROR("incompatible ruleset schema version {}.x", version); - - throw unsupported_version(); -} - -waf *waf::update(ddwaf::parameter input, ddwaf::base_ruleset_info &info) -{ - if (builder_) { - auto ruleset = builder_->build(input, info); - if (ruleset) { - return new waf{builder_, std::move(ruleset)}; - } - } - return nullptr; -} - -} // namespace ddwaf diff --git a/src/waf.hpp b/src/waf.hpp index d1f6cd594..a90a231dd 100644 --- a/src/waf.hpp +++ b/src/waf.hpp @@ -9,9 +9,8 @@ #include "context.hpp" #include "ddwaf.h" -#include "parser/parser.hpp" +#include "parameter.hpp" #include "ruleset.hpp" -#include "ruleset_builder.hpp" #include "ruleset_info.hpp" #include "utils.hpp" #include "version.hpp" @@ -20,9 +19,12 @@ namespace ddwaf { class waf { public: - waf(ddwaf::parameter input, ddwaf::base_ruleset_info &info, ddwaf::object_limits limits, - ddwaf_object_free_fn free_fn, std::shared_ptr event_obfuscator); - waf *update(ddwaf::parameter input, ddwaf::base_ruleset_info &info); + explicit waf(std::shared_ptr ruleset) : ruleset_(std::move(ruleset)) {} + waf(const waf &) = default; + waf(waf &&) = default; + waf &operator=(const waf &) = default; + waf &operator=(waf &&) = default; + ~waf() = default; ddwaf::context_wrapper *create_context() { return new context_wrapper(ruleset_); } @@ -37,11 +39,6 @@ class waf { } protected: - waf(std::shared_ptr builder, std::shared_ptr ruleset) - : builder_(std::move(builder)), ruleset_(std::move(ruleset)) - {} - - std::shared_ptr builder_; std::shared_ptr ruleset_; }; diff --git a/tests/common/base_utils.hpp b/tests/common/base_utils.hpp index 4dbaaa8bb..332c259e2 100644 --- a/tests/common/base_utils.hpp +++ b/tests/common/base_utils.hpp @@ -39,6 +39,8 @@ NULL, 0, {string}, length, DDWAF_OBJ_STRING \ } +#define LSTRARG(value) value, sizeof(value) - 1 + namespace ddwaf::test { class expression_builder { diff --git a/tests/integration/actions/test.cpp b/tests/integration/actions/test.cpp index b6279e36a..79f5f32ef 100644 --- a/tests/integration/actions/test.cpp +++ b/tests/integration/actions/test.cpp @@ -147,12 +147,17 @@ TEST(TestActionsIntegration, DefaultActions) TEST(TestActionsIntegration, OverrideDefaultAction) { - auto rule = read_file("default_actions.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("default_actions.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object tmp; { @@ -185,16 +190,17 @@ TEST(TestActionsIntegration, OverrideDefaultAction) } { - auto overrides = yaml_to_object( - R"({actions: [{id: block, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); - auto *new_handle = ddwaf_update(handle, &overrides, nullptr); - ddwaf_object_free(&overrides); - ASSERT_NE(new_handle, nullptr); - ddwaf_destroy(handle); - handle = new_handle; + + auto actions = yaml_to_object( + R"({actions: [{id: block, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); + ddwaf_builder_add_or_update_config(builder, LSTRARG("actions"), &actions, nullptr); + ddwaf_object_free(&actions); } + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + { ddwaf_object parameter = DDWAF_OBJECT_MAP; ddwaf_object_map_add(¶meter, "value", ddwaf_object_string(&tmp, "block")); @@ -224,16 +230,22 @@ TEST(TestActionsIntegration, OverrideDefaultAction) ddwaf_context_destroy(context); } ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestActionsIntegration, AddNewAction) { - auto rule = read_file("default_actions.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("default_actions.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object tmp; { @@ -265,16 +277,17 @@ TEST(TestActionsIntegration, AddNewAction) } { - auto overrides = yaml_to_object( - R"({actions: [{id: unblock, type: unblock_request, parameters: {code: 303}}]})"); - auto *new_handle = ddwaf_update(handle, &overrides, nullptr); - ddwaf_object_free(&overrides); - ASSERT_NE(new_handle, nullptr); - ddwaf_destroy(handle); - handle = new_handle; + + auto actions = yaml_to_object( + R"({actions: [{id: unblock, type: unblock_request, parameters: {code: 303}}]})"); + ddwaf_builder_add_or_update_config(builder, LSTRARG("actions"), &actions, nullptr); + ddwaf_object_free(&actions); } + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + { ddwaf_object parameter = DDWAF_OBJECT_MAP; ddwaf_object_map_add(¶meter, "value", ddwaf_object_string(&tmp, "unblock")); @@ -305,6 +318,7 @@ TEST(TestActionsIntegration, AddNewAction) } ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestActionsIntegration, EmptyOrInvalidActions) diff --git a/tests/integration/custom_rules/test.cpp b/tests/integration/custom_rules/test.cpp index 36d230182..af42249f1 100644 --- a/tests/integration/custom_rules/test.cpp +++ b/tests/integration/custom_rules/test.cpp @@ -224,21 +224,28 @@ TEST(TestCustomRulesIntegration, CustomRulesPrecedence) // Custom rules can be updated when base rules exist TEST(TestCustomRulesIntegration, UpdateFromBaseRules) { - auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); + + { + auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("base_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - auto update = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -297,28 +304,36 @@ TEST(TestCustomRulesIntegration, UpdateFromBaseRules) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); + + ddwaf_builder_destroy(builder); } // Custom rules can be updated when custom rules already exist TEST(TestCustomRulesIntegration, UpdateFromCustomRules) { - auto rule = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); - ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); + { + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - auto update = yaml_to_object( - R"({custom_rules: [{id: custom_rule5, name: custom_rule5, tags: {type: flow5, category: category5}, conditions: [{operator: match_regex, parameters: {inputs: [{address: value34}], regex: custom_rule}}]}]})"); + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle1, nullptr); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto rule = yaml_to_object( + R"({custom_rules: [{id: custom_rule5, name: custom_rule5, tags: {type: flow5, category: category5}, conditions: [{operator: match_regex, parameters: {inputs: [{address: value34}], regex: custom_rule}}]}]})"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -374,49 +389,59 @@ TEST(TestCustomRulesIntegration, UpdateFromCustomRules) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); + + ddwaf_builder_destroy(builder); } // Remove all custom rules when no other rules are available TEST(TestCustomRulesIntegration, UpdateWithEmptyRules) { - auto rule = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); - ASSERT_NE(handle1, nullptr); + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); ddwaf_object_free(&rule); - auto update = yaml_to_object(R"({custom_rules: []})"); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle1, nullptr); - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_builder_remove_config(builder, LSTRARG("custom_rules")); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_EQ(handle2, nullptr); - ddwaf_object_free(&update); // Destroying the handle should not invalidate it ddwaf_destroy(handle1); + + ddwaf_builder_destroy(builder); } // Remove all custom rules when base rules are available TEST(TestCustomRulesIntegration, UpdateRemoveAllCustomRules) { - auto rule = read_file("custom_rules_and_rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); - ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); + { + auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("base_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - auto update = yaml_to_object(R"({custom_rules: []})"); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle1, nullptr); - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_builder_remove_config(builder, LSTRARG("custom_rules")); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -475,27 +500,36 @@ TEST(TestCustomRulesIntegration, UpdateRemoveAllCustomRules) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); + + ddwaf_builder_destroy(builder); } // Ensure that existing custom rules are unaffected by overrides TEST(TestCustomRulesIntegration, CustomRulesUnaffectedByOverrides) { - auto rule = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - auto update = yaml_to_object( - R"({rules_override: [{rules_target: [{tags: {category: category4}}], enabled: false}]})"); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{tags: {category: category4}}], enabled: false}]})"); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); + ddwaf_object_free(&overrides); + } - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -554,34 +588,46 @@ TEST(TestCustomRulesIntegration, CustomRulesUnaffectedByOverrides) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); + + ddwaf_builder_destroy(builder); } // Ensure that custom rules are unaffected by overrides after an update TEST(TestCustomRulesIntegration, CustomRulesUnaffectedByOverridesAfterUpdate) { - auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("base_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - auto update = yaml_to_object( - R"({rules_override: [{rules_target: [{tags: {category: category4}}], enabled: false}]})"); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto overrides = yaml_to_object( + R"({rules_override: [{rules_target: [{tags: {category: category4}}], enabled: false}]})"); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); + ddwaf_object_free(&overrides); + } - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); - auto rules_update = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(rules_update.type != DDWAF_OBJ_INVALID); + { + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - ddwaf_handle handle3 = ddwaf_update(handle2, &rules_update, nullptr); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle3, nullptr); - ddwaf_object_free(&rules_update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -654,27 +700,36 @@ TEST(TestCustomRulesIntegration, CustomRulesUnaffectedByOverridesAfterUpdate) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); ddwaf_context_destroy(context3); + + ddwaf_builder_destroy(builder); } // Ensure that existing custom rules are unaffected by overrides TEST(TestCustomRulesIntegration, CustomRulesAffectedByExclusions) { - auto rule = read_file("custom_rules_and_rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); + + { + auto rule = read_file("custom_rules_and_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - auto update = yaml_to_object( - R"({exclusions: [{id: custom_rule4_exclude, rules_target: [{rule_id: custom_rule4}]}]})"); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto exclusions = yaml_to_object( + R"({exclusions: [{id: custom_rule4_exclude, rules_target: [{rule_id: custom_rule4}]}]})"); + ASSERT_TRUE(exclusions.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusions"), &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -733,34 +788,46 @@ TEST(TestCustomRulesIntegration, CustomRulesAffectedByExclusions) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); + + ddwaf_builder_destroy(builder); } // Ensure that custom rules are affected by overrides after an update TEST(TestCustomRulesIntegration, CustomRulesAffectedByExclusionsAfterUpdate) { - auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("custom_rules_base_rules_only.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("base_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - auto update = yaml_to_object( - R"({exclusions: [{id: custom_rule4_exclude, rules_target: [{tags: {category: category4}}]}]})"); - ASSERT_TRUE(update.type != DDWAF_OBJ_INVALID); + { + auto exclusions = yaml_to_object( + R"({exclusions: [{id: custom_rule4_exclude, rules_target: [{tags: {category: category4}}]}]})"); + ASSERT_TRUE(exclusions.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusions"), &exclusions, nullptr); + ddwaf_object_free(&exclusions); + } - ddwaf_handle handle2 = ddwaf_update(handle1, &update, nullptr); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&update); - auto rules_update = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(rules_update.type != DDWAF_OBJ_INVALID); + { + auto rule = read_file("custom_rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("custom_rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } - ddwaf_handle handle3 = ddwaf_update(handle2, &rules_update, nullptr); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle3, nullptr); - ddwaf_object_free(&rules_update); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -849,6 +916,8 @@ TEST(TestCustomRulesIntegration, CustomRulesAffectedByExclusionsAfterUpdate) ddwaf_context_destroy(context1); ddwaf_context_destroy(context2); ddwaf_context_destroy(context3); + + ddwaf_builder_destroy(builder); } } // namespace diff --git a/tests/integration/diagnostics/v1/test.cpp b/tests/integration/diagnostics/v1/test.cpp index a2e504efc..4b880810b 100644 --- a/tests/integration/diagnostics/v1/test.cpp +++ b/tests/integration/diagnostics/v1/test.cpp @@ -5,8 +5,7 @@ // Copyright 2021 Datadog, Inc. #include "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" +#include "configuration/common/common.hpp" using namespace ddwaf; @@ -80,18 +79,18 @@ TEST(TestDiagnosticsV1Integration, Basic) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); ddwaf_object_free(&diagnostics); @@ -115,18 +114,18 @@ TEST(TestDiagnosticsV1Integration, TestInvalidRule) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 1); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("missing key 'type'"); @@ -153,18 +152,18 @@ TEST(TestDiagnosticsV1Integration, TestMultipleSameInvalidRules) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 2); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("missing key 'type'"); @@ -192,18 +191,18 @@ TEST(TestDiagnosticsV1Integration, TestMultipleDiffInvalidRules) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 2); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 2); { @@ -241,18 +240,18 @@ TEST(TestDiagnosticsV1Integration, TestMultipleMixInvalidRules) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 4); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 3); { @@ -301,18 +300,18 @@ TEST(TestDiagnosticsV1Integration, TestInvalidDuplicate) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 1); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("duplicate rule"); @@ -340,18 +339,18 @@ TEST(TestDiagnosticsV1Integration, TestInvalidTooManyTransformers) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version", ""); + auto version = ddwaf::at(root_map, "ruleset_version", ""); EXPECT_STREQ(version.c_str(), ""); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 1); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("number of transformers beyond allowed limit"); @@ -365,4 +364,28 @@ TEST(TestDiagnosticsV1Integration, TestInvalidTooManyTransformers) ddwaf_destroy(handle); } +TEST(TestDiagnosticsV1Integration, InvalidRulesContainer) +{ + auto rule = yaml_to_object(R"({version: '1.1', events: {}})"); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_handle handle = ddwaf_init(&rule, nullptr, &diagnostics); + ASSERT_EQ(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version", ""); + EXPECT_STREQ(version.c_str(), ""); + + auto rules = ddwaf::at(root_map, "rules"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); +} + } // namespace diff --git a/tests/integration/diagnostics/v2/test.cpp b/tests/integration/diagnostics/v2/test.cpp index ded1f4d8e..489187195 100644 --- a/tests/integration/diagnostics/v2/test.cpp +++ b/tests/integration/diagnostics/v2/test.cpp @@ -6,9 +6,9 @@ #include "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "ddwaf.h" #include "parameter.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" using namespace ddwaf; @@ -19,7 +19,7 @@ TEST(TestDiagnosticsV2Integration, BasicRule) { auto rule = yaml_to_object( 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); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -30,19 +30,19 @@ TEST(TestDiagnosticsV2Integration, BasicRule) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "1.2.7"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_NE(loaded.find("1"), loaded.end()); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); ddwaf_object_free(&diagnostics); @@ -52,67 +52,63 @@ TEST(TestDiagnosticsV2Integration, BasicRule) TEST(TestDiagnosticsV2Integration, BasicRuleWithUpdate) { + ddwaf_builder builder = ddwaf_builder_init(nullptr); + auto rule = yaml_to_object( 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); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, &diagnostics); - ASSERT_NE(handle, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); { - ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "1.2.7"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_NE(loaded.find("1"), loaded.end()); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); ddwaf_object_free(&diagnostics); } - ddwaf_handle new_handle = ddwaf_update(handle, &rule, &diagnostics); - ASSERT_NE(handle, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); { ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "1.2.7"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_NE(loaded.find("1"), loaded.end()); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); ddwaf_object_free(&diagnostics); } ddwaf_object_free(&rule); - - ddwaf_destroy(handle); - ddwaf_destroy(new_handle); + ddwaf_builder_destroy(builder); } TEST(TestDiagnosticsV2Integration, NullRuleset) @@ -128,7 +124,7 @@ TEST(TestDiagnosticsV2Integration, NullRuleset) TEST(TestDiagnosticsV2Integration, InvalidRule) { auto rule = read_file("invalid_single.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -139,16 +135,16 @@ TEST(TestDiagnosticsV2Integration, InvalidRule) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 1); EXPECT_NE(failed.find("1"), failed.end()); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("missing key 'type'"); @@ -164,7 +160,7 @@ TEST(TestDiagnosticsV2Integration, InvalidRule) TEST(TestDiagnosticsV2Integration, MultipleSameInvalidRules) { auto rule = read_file("invalid_multiple_same.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -175,17 +171,17 @@ TEST(TestDiagnosticsV2Integration, MultipleSameInvalidRules) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 2); EXPECT_NE(failed.find("1"), failed.end()); EXPECT_NE(failed.find("2"), failed.end()); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("missing key 'type'"); @@ -202,7 +198,7 @@ TEST(TestDiagnosticsV2Integration, MultipleSameInvalidRules) TEST(TestDiagnosticsV2Integration, MultipleDiffInvalidRules) { auto rule = read_file("invalid_multiple_diff.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -213,17 +209,17 @@ TEST(TestDiagnosticsV2Integration, MultipleDiffInvalidRules) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 2); EXPECT_NE(failed.find("1"), failed.end()); EXPECT_NE(failed.find("2"), failed.end()); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 2); { @@ -250,7 +246,7 @@ TEST(TestDiagnosticsV2Integration, MultipleDiffInvalidRules) TEST(TestDiagnosticsV2Integration, MultipleMixInvalidRules) { auto rule = read_file("invalid_multiple_mix.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -261,20 +257,20 @@ TEST(TestDiagnosticsV2Integration, MultipleMixInvalidRules) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_NE(loaded.find("5"), loaded.end()); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 4); EXPECT_NE(failed.find("1"), failed.end()); EXPECT_NE(failed.find("2"), failed.end()); EXPECT_NE(failed.find("3"), failed.end()); EXPECT_NE(failed.find("4"), failed.end()); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 3); { @@ -313,7 +309,7 @@ TEST(TestDiagnosticsV2Integration, MultipleMixInvalidRules) TEST(TestDiagnosticsV2Integration, InvalidDuplicate) { auto rule = read_file("invalid_duplicate.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -324,17 +320,17 @@ TEST(TestDiagnosticsV2Integration, InvalidDuplicate) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_NE(loaded.find("1"), loaded.end()); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 1); EXPECT_NE(failed.find("1"), failed.end()); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 1); auto it = errors.find("duplicate rule"); @@ -352,7 +348,7 @@ TEST(TestDiagnosticsV2Integration, InvalidDuplicate) TEST(TestDiagnosticsV2Integration, InvalidRuleset) { auto rule = read_file("invalid_ruleset.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_object diagnostics; @@ -363,15 +359,15 @@ TEST(TestDiagnosticsV2Integration, InvalidRuleset) ddwaf::parameter root(diagnostics); auto root_map = static_cast(root); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 400); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 20); for (auto &[key, value] : errors) { @@ -385,7 +381,7 @@ TEST(TestDiagnosticsV2Integration, InvalidRuleset) TEST(TestDiagnosticsV2Integration, MultipleRules) { auto rule = read_file("rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -398,32 +394,32 @@ TEST(TestDiagnosticsV2Integration, MultipleRules) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STR(version, "5.4.2"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); EXPECT_EQ(rules.size(), 5); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 4); EXPECT_TRUE(loaded.contains("rule1")); EXPECT_TRUE(loaded.contains("rule2")); EXPECT_TRUE(loaded.contains("rule3")); EXPECT_TRUE(loaded.contains("rule4")); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(rules, "skipped"); + auto skipped = ddwaf::at(rules, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(rules, "addresses"); + auto addresses = ddwaf::at(rules, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 5); EXPECT_TRUE(required.contains("value1")); EXPECT_TRUE(required.contains("value2")); @@ -431,7 +427,7 @@ TEST(TestDiagnosticsV2Integration, MultipleRules) EXPECT_TRUE(required.contains("value4")); EXPECT_TRUE(required.contains("value34")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 0); ddwaf_object_free(&root); @@ -443,7 +439,7 @@ TEST(TestDiagnosticsV2Integration, MultipleRules) TEST(TestDiagnosticsV2Integration, RulesWithMinVersion) { auto rule = read_file("rules_min_version.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -456,34 +452,34 @@ TEST(TestDiagnosticsV2Integration, RulesWithMinVersion) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STR(version, "5.4.2"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); EXPECT_EQ(rules.size(), 5); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("rule1")); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(rules, "skipped"); + auto skipped = ddwaf::at(rules, "skipped"); EXPECT_EQ(skipped.size(), 1); EXPECT_TRUE(skipped.contains("rule2")); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(rules, "addresses"); + auto addresses = ddwaf::at(rules, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 1); EXPECT_TRUE(required.contains("value1")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 0); ddwaf_object_free(&root); @@ -495,7 +491,7 @@ TEST(TestDiagnosticsV2Integration, RulesWithMinVersion) TEST(TestDiagnosticsV2Integration, RulesWithMaxVersion) { auto rule = read_file("rules_max_version.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -508,34 +504,34 @@ TEST(TestDiagnosticsV2Integration, RulesWithMaxVersion) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STR(version, "5.4.2"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); EXPECT_EQ(rules.size(), 5); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("rule1")); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(rules, "skipped"); + auto skipped = ddwaf::at(rules, "skipped"); EXPECT_EQ(skipped.size(), 1); EXPECT_TRUE(skipped.contains("rule2")); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(rules, "addresses"); + auto addresses = ddwaf::at(rules, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 1); EXPECT_TRUE(required.contains("value1")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 0); ddwaf_object_free(&root); @@ -547,7 +543,7 @@ TEST(TestDiagnosticsV2Integration, RulesWithMaxVersion) TEST(TestDiagnosticsV2Integration, RulesWithMinMaxVersion) { auto rule = read_file("rules_min_max_version.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -560,35 +556,35 @@ TEST(TestDiagnosticsV2Integration, RulesWithMinMaxVersion) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STR(version, "5.4.2"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); EXPECT_EQ(rules.size(), 5); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("rule1")); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(rules, "skipped"); + auto skipped = ddwaf::at(rules, "skipped"); EXPECT_EQ(skipped.size(), 2); EXPECT_TRUE(skipped.contains("rule2")); EXPECT_TRUE(skipped.contains("rule3")); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(rules, "addresses"); + auto addresses = ddwaf::at(rules, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 1); EXPECT_TRUE(required.contains("value1")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 0); ddwaf_object_free(&root); @@ -600,7 +596,7 @@ TEST(TestDiagnosticsV2Integration, RulesWithMinMaxVersion) TEST(TestDiagnosticsV2Integration, RulesWithErrors) { auto rule = read_file("rules_with_errors.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -613,20 +609,20 @@ TEST(TestDiagnosticsV2Integration, RulesWithErrors) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STR(version, "5.4.1"); - auto rules = ddwaf::parser::at(root_map, "rules"); + auto rules = ddwaf::at(root_map, "rules"); EXPECT_EQ(rules.size(), 5); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("rule1")); - auto skipped = ddwaf::parser::at(rules, "skipped"); + auto skipped = ddwaf::at(rules, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 5); EXPECT_TRUE(failed.contains("rule1")); EXPECT_TRUE(failed.contains("index:2")); @@ -634,7 +630,7 @@ TEST(TestDiagnosticsV2Integration, RulesWithErrors) EXPECT_TRUE(failed.contains("rule5")); EXPECT_TRUE(failed.contains("rule6")); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 4); { @@ -674,14 +670,14 @@ TEST(TestDiagnosticsV2Integration, RulesWithErrors) EXPECT_TRUE(error_rules.contains("rule6")); } - auto addresses = ddwaf::parser::at(rules, "addresses"); + auto addresses = ddwaf::at(rules, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 1); EXPECT_TRUE(required.contains("value1")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 0); ddwaf_object_free(&root); @@ -693,7 +689,7 @@ TEST(TestDiagnosticsV2Integration, RulesWithErrors) TEST(TestDiagnosticsV2Integration, CustomRules) { auto rule = read_file("custom_rules.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -706,32 +702,32 @@ TEST(TestDiagnosticsV2Integration, CustomRules) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STR(version, "5.4.3"); - auto rules = ddwaf::parser::at(root_map, "custom_rules"); + auto rules = ddwaf::at(root_map, "custom_rules"); EXPECT_EQ(rules.size(), 5); - auto loaded = ddwaf::parser::at(rules, "loaded"); + auto loaded = ddwaf::at(rules, "loaded"); EXPECT_EQ(loaded.size(), 4); EXPECT_TRUE(loaded.contains("custom_rule1")); EXPECT_TRUE(loaded.contains("custom_rule2")); EXPECT_TRUE(loaded.contains("custom_rule3")); EXPECT_TRUE(loaded.contains("custom_rule4")); - auto failed = ddwaf::parser::at(rules, "failed"); + auto failed = ddwaf::at(rules, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(rules, "skipped"); + auto skipped = ddwaf::at(rules, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(rules, "errors"); + auto errors = ddwaf::at(rules, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(rules, "addresses"); + auto addresses = ddwaf::at(rules, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 5); EXPECT_TRUE(required.contains("value1")); EXPECT_TRUE(required.contains("value2")); @@ -739,7 +735,7 @@ TEST(TestDiagnosticsV2Integration, CustomRules) EXPECT_TRUE(required.contains("value4")); EXPECT_TRUE(required.contains("value34")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 0); ddwaf_object_free(&root); @@ -751,7 +747,7 @@ TEST(TestDiagnosticsV2Integration, CustomRules) TEST(TestDiagnosticsV2Integration, InputFilter) { auto rule = read_file("input_filter.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -764,30 +760,30 @@ TEST(TestDiagnosticsV2Integration, InputFilter) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto exclusions = ddwaf::parser::at(root_map, "exclusions"); + auto exclusions = ddwaf::at(root_map, "exclusions"); EXPECT_EQ(exclusions.size(), 5); - auto loaded = ddwaf::parser::at(exclusions, "loaded"); + auto loaded = ddwaf::at(exclusions, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("1")); - auto failed = ddwaf::parser::at(exclusions, "failed"); + auto failed = ddwaf::at(exclusions, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(exclusions, "skipped"); + auto skipped = ddwaf::at(exclusions, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(exclusions, "errors"); + auto errors = ddwaf::at(exclusions, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(exclusions, "addresses"); + auto addresses = ddwaf::at(exclusions, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 1); EXPECT_TRUE(required.contains("exclusion-filter-1-input")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 2); EXPECT_TRUE(optional.contains("rule1-input1")); EXPECT_TRUE(optional.contains("rule1-input2")); @@ -801,7 +797,7 @@ TEST(TestDiagnosticsV2Integration, InputFilter) TEST(TestDiagnosticsV2Integration, RuleData) { auto rule = read_file("rule_data.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -814,21 +810,21 @@ TEST(TestDiagnosticsV2Integration, RuleData) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto rule_data = ddwaf::parser::at(root_map, "rules_data"); + auto rule_data = ddwaf::at(root_map, "rules_data"); EXPECT_EQ(rule_data.size(), 4); - auto loaded = ddwaf::parser::at(rule_data, "loaded"); + auto loaded = ddwaf::at(rule_data, "loaded"); EXPECT_EQ(loaded.size(), 2); EXPECT_TRUE(loaded.contains("ip_data")); EXPECT_TRUE(loaded.contains("usr_data")); - auto failed = ddwaf::parser::at(rule_data, "failed"); + auto failed = ddwaf::at(rule_data, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(rule_data, "skipped"); + auto skipped = ddwaf::at(rule_data, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(rule_data, "errors"); + auto errors = ddwaf::at(rule_data, "errors"); EXPECT_EQ(errors.size(), 0); ddwaf_object_free(&root); @@ -840,7 +836,7 @@ TEST(TestDiagnosticsV2Integration, RuleData) TEST(TestDiagnosticsV2Integration, Processor) { auto rule = read_json_file("processor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; @@ -853,30 +849,30 @@ TEST(TestDiagnosticsV2Integration, Processor) ddwaf::parameter root = diagnostics; auto root_map = static_cast(root); - auto processor = ddwaf::parser::at(root_map, "processors"); + auto processor = ddwaf::at(root_map, "processors"); EXPECT_EQ(processor.size(), 5); - auto loaded = ddwaf::parser::at(processor, "loaded"); + auto loaded = ddwaf::at(processor, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_TRUE(loaded.contains("processor-001")); - auto failed = ddwaf::parser::at(processor, "failed"); + auto failed = ddwaf::at(processor, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(processor, "skipped"); + auto skipped = ddwaf::at(processor, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(processor, "errors"); + auto errors = ddwaf::at(processor, "errors"); EXPECT_EQ(errors.size(), 0); - auto addresses = ddwaf::parser::at(processor, "addresses"); + auto addresses = ddwaf::at(processor, "addresses"); EXPECT_EQ(addresses.size(), 2); - auto required = ddwaf::parser::at(addresses, "required"); + auto required = ddwaf::at(addresses, "required"); EXPECT_EQ(required.size(), 1); EXPECT_TRUE(required.contains("waf.context.processor")); - auto optional = ddwaf::parser::at(addresses, "optional"); + auto optional = ddwaf::at(addresses, "optional"); EXPECT_EQ(optional.size(), 1); EXPECT_TRUE(optional.contains("server.request.body")); @@ -886,4 +882,274 @@ TEST(TestDiagnosticsV2Integration, Processor) ddwaf_destroy(handle); } +TEST(TestDiagnosticsV2Integration, InvalidRulesContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, rules: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "rules"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidCustomRulesContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, custom_rules: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "custom_rules"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidExclusionsContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, exclusions: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "exclusions"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidOverridesContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = yaml_to_object( + R"({version: '2.1', metadata: {rules_version: '1.2.7'}, rules_override: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "rules_override"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidScannersContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, scanners: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "scanners"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidProcessorsContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, processors: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "processors"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidActionsContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, actions: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "actions"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidRuleDataContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = + yaml_to_object(R"({version: '2.1', metadata: {rules_version: '1.2.7'}, rules_data: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "rules_data"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + +TEST(TestDiagnosticsV2Integration, InvalidExclusionDataContainer) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + auto rule = yaml_to_object( + R"({version: '2.1', metadata: {rules_version: '1.2.7'}, exclusion_data: {}})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + + ddwaf_object diagnostics; + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, &diagnostics); + ddwaf_object_free(&rule); + + { + ddwaf::parameter root(diagnostics); + auto root_map = static_cast(root); + + auto version = ddwaf::at(root_map, "ruleset_version"); + EXPECT_STREQ(version.c_str(), "1.2.7"); + + auto rules = ddwaf::at(root_map, "exclusion_data"); + + auto errors = ddwaf::at(rules, "error"); + EXPECT_STR(errors, "bad cast, expected 'array', obtained 'map'"); + + ddwaf_object_free(&diagnostics); + } + + ddwaf_builder_destroy(builder); +} + } // namespace diff --git a/tests/integration/exclusion/rule_filter/test.cpp b/tests/integration/exclusion/rule_filter/test.cpp index e9aca7197..517019dc6 100644 --- a/tests/integration/exclusion/rule_filter/test.cpp +++ b/tests/integration/exclusion/rule_filter/test.cpp @@ -16,7 +16,7 @@ constexpr std::string_view base_dir = "integration/exclusion/rule_filter/"; TEST(TestRuleFilterIntegration, ExcludeSingleRule) { auto rule = read_file("exclude_one_rule.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -49,7 +49,7 @@ TEST(TestRuleFilterIntegration, ExcludeSingleRule) TEST(TestRuleFilterIntegration, ExcludeByType) { auto rule = read_file("exclude_by_type.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -82,7 +82,7 @@ TEST(TestRuleFilterIntegration, ExcludeByType) TEST(TestRuleFilterIntegration, ExcludeByCategory) { auto rule = read_file("exclude_by_category.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -107,7 +107,7 @@ TEST(TestRuleFilterIntegration, ExcludeByCategory) TEST(TestRuleFilterIntegration, ExcludeByTags) { auto rule = read_file("exclude_by_tags.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -140,7 +140,7 @@ TEST(TestRuleFilterIntegration, ExcludeByTags) TEST(TestRuleFilterIntegration, ExcludeAllWithCondition) { auto rule = read_file("exclude_all_with_condition.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -202,7 +202,7 @@ TEST(TestRuleFilterIntegration, ExcludeAllWithCondition) TEST(TestRuleFilterIntegration, ExcludeSingleRuleWithCondition) { auto rule = read_file("exclude_one_rule_with_condition.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -273,7 +273,7 @@ TEST(TestRuleFilterIntegration, ExcludeSingleRuleWithCondition) TEST(TestRuleFilterIntegration, ExcludeSingleRuleWithConditionAndTransformers) { auto rule = read_file("exclude_one_rule_with_condition_and_transformers.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -343,7 +343,7 @@ TEST(TestRuleFilterIntegration, ExcludeSingleRuleWithConditionAndTransformers) TEST(TestRuleFilterIntegration, ExcludeByTypeWithCondition) { auto rule = read_file("exclude_by_type_with_condition.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -414,7 +414,7 @@ TEST(TestRuleFilterIntegration, ExcludeByTypeWithCondition) TEST(TestRuleFilterIntegration, ExcludeByCategoryWithCondition) { auto rule = read_file("exclude_by_category_with_condition.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -476,7 +476,7 @@ TEST(TestRuleFilterIntegration, ExcludeByCategoryWithCondition) TEST(TestRuleFilterIntegration, ExcludeByTagsWithCondition) { auto rule = read_file("exclude_by_tags_with_condition.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -547,7 +547,7 @@ TEST(TestRuleFilterIntegration, ExcludeByTagsWithCondition) TEST(TestRuleFilterIntegration, MonitorSingleRule) { auto rule = read_file("monitor_one_rule.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -583,7 +583,7 @@ TEST(TestRuleFilterIntegration, MonitorSingleRule) TEST(TestRuleFilterIntegration, AvoidHavingTwoMonitorOnActions) { auto rule = read_file("multiple_monitor_on_match.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -619,7 +619,7 @@ TEST(TestRuleFilterIntegration, AvoidHavingTwoMonitorOnActions) TEST(TestRuleFilterIntegration, MonitorBypassFilterModePrecedence) { auto rule = read_file("monitor_bypass_precedence.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -641,7 +641,7 @@ TEST(TestRuleFilterIntegration, MonitorBypassFilterModePrecedence) TEST(TestRuleFilterIntegration, MonitorCustomFilterModePrecedence) { auto rule = read_file("monitor_custom_precedence.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -677,7 +677,7 @@ TEST(TestRuleFilterIntegration, MonitorCustomFilterModePrecedence) TEST(TestRuleFilterIntegration, BypassCustomFilterModePrecedence) { auto rule = read_file("bypass_custom_precedence.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -700,7 +700,7 @@ TEST(TestRuleFilterIntegration, BypassCustomFilterModePrecedence) TEST(TestRuleFilterIntegration, UnconditionalCustomFilterMode) { auto rule = read_file("exclude_with_custom_action.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -737,7 +737,7 @@ TEST(TestRuleFilterIntegration, UnconditionalCustomFilterMode) TEST(TestRuleFilterIntegration, ConditionalCustomFilterMode) { auto rule = read_file("exclude_with_custom_action_and_condition.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); @@ -801,12 +801,17 @@ TEST(TestRuleFilterIntegration, ConditionalCustomFilterMode) TEST(TestRuleFilterIntegration, CustomFilterModeUnknownAction) { - auto rule = read_file("exclude_with_unknown_action.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - auto *handle1 = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("exclude_with_unknown_action.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("default"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { ddwaf_context context = ddwaf_context_init(handle1); @@ -834,15 +839,16 @@ TEST(TestRuleFilterIntegration, CustomFilterModeUnknownAction) ddwaf_context_destroy(context); } - ddwaf_handle handle2; { - auto overrides = + auto actions = yaml_to_object(R"({actions: [{id: block2, type: block_request, parameters: {}}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); - ddwaf_object_free(&overrides); - ASSERT_NE(handle2, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("actions"), &actions, nullptr); + ddwaf_object_free(&actions); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle1, nullptr); + { ddwaf_context context = ddwaf_context_init(handle2); ASSERT_NE(context, nullptr); @@ -873,6 +879,7 @@ TEST(TestRuleFilterIntegration, CustomFilterModeUnknownAction) ddwaf_destroy(handle1); ddwaf_destroy(handle2); + ddwaf_builder_destroy(builder); } TEST(TestRuleFilterIntegration, CustomFilterModeNonblockingAction) @@ -881,7 +888,7 @@ TEST(TestRuleFilterIntegration, CustomFilterModeNonblockingAction) // generate_stack, which is neither a blocking, redirecting or monitoring // action, hence its ignored. auto rule = read_file("exclude_with_nonblocking_action.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); auto *handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); diff --git a/tests/integration/exclusion_data/test.cpp b/tests/integration/exclusion_data/test.cpp index 5b5bee97c..cc83e3097 100644 --- a/tests/integration/exclusion_data/test.cpp +++ b/tests/integration/exclusion_data/test.cpp @@ -14,12 +14,17 @@ constexpr std::string_view base_dir = "integration/exclusion_data/"; TEST(TestExclusionDataIntegration, ExcludeRuleByUserID) { - auto rule = read_file("exclude_one_rule_by_user.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("exclude_one_rule_by_user.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { ddwaf_context context = ddwaf_context_init(handle1); @@ -57,16 +62,16 @@ TEST(TestExclusionDataIntegration, ExcludeRuleByUserID) ddwaf_context_destroy(context); } - ddwaf_handle handle2; { - auto root = yaml_to_object( + auto data = yaml_to_object( R"({exclusion_data: [{id: usr_data, type: data_with_expiration, data: [{value: admin, expiration: 0}]}]})"); - - handle2 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&root); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusion_data"), &data, nullptr); + ddwaf_object_free(&data); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context = ddwaf_context_init(handle2); ASSERT_NE(context, nullptr); @@ -93,14 +98,9 @@ TEST(TestExclusionDataIntegration, ExcludeRuleByUserID) ddwaf_context_destroy(context); } - ddwaf_handle handle3; - { - auto root = yaml_to_object(R"({exclusion_data: []})"); - - handle3 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle3, nullptr); - ddwaf_object_free(&root); - } + ddwaf_builder_remove_config(builder, LSTRARG("exclusion_data")); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { ddwaf_context context = ddwaf_context_init(handle3); @@ -141,16 +141,23 @@ TEST(TestExclusionDataIntegration, ExcludeRuleByUserID) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestExclusionDataIntegration, ExcludeRuleByClientIP) { - auto rule = read_file("exclude_one_rule_by_ip.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("exclude_one_rule_by_ip.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { ddwaf_context context = ddwaf_context_init(handle1); @@ -188,16 +195,16 @@ TEST(TestExclusionDataIntegration, ExcludeRuleByClientIP) ddwaf_context_destroy(context); } - ddwaf_handle handle2; { - auto root = yaml_to_object( + auto data = yaml_to_object( R"({exclusion_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.0.1, expiration: 0}]}]})"); - - handle2 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&root); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusion_data"), &data, nullptr); + ddwaf_object_free(&data); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context = ddwaf_context_init(handle2); ASSERT_NE(context, nullptr); @@ -224,14 +231,9 @@ TEST(TestExclusionDataIntegration, ExcludeRuleByClientIP) ddwaf_context_destroy(context); } - ddwaf_handle handle3; - { - auto root = yaml_to_object(R"({exclusion_data: []})"); - - handle3 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle3, nullptr); - ddwaf_object_free(&root); - } + ddwaf_builder_remove_config(builder, LSTRARG("exclusion_data")); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { ddwaf_context context = ddwaf_context_init(handle3); @@ -271,16 +273,23 @@ TEST(TestExclusionDataIntegration, ExcludeRuleByClientIP) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestExclusionDataIntegration, UnknownDataTypeOnExclusionData) { - auto rule = read_file("exclude_one_rule_by_ip.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("exclude_one_rule_by_ip.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { ddwaf_context context = ddwaf_context_init(handle1); @@ -318,16 +327,16 @@ TEST(TestExclusionDataIntegration, UnknownDataTypeOnExclusionData) ddwaf_context_destroy(context); } - ddwaf_handle handle2; { - auto root = yaml_to_object( + auto data = yaml_to_object( R"({exclusion_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.0.1, expiration: 0}]}]})"); - - handle2 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&root); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusion_data"), &data, nullptr); + ddwaf_object_free(&data); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context = ddwaf_context_init(handle2); ASSERT_NE(context, nullptr); @@ -354,16 +363,16 @@ TEST(TestExclusionDataIntegration, UnknownDataTypeOnExclusionData) ddwaf_context_destroy(context); } - ddwaf_handle handle3; { - auto root = + auto data = yaml_to_object(R"({exclusion_data: [{id: ip_data, type: unknown_data, data: [{}]}]})"); - - handle3 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle3, nullptr); - ddwaf_object_free(&root); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusion_data"), &data, nullptr); + ddwaf_object_free(&data); } + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); + { ddwaf_context context = ddwaf_context_init(handle3); ASSERT_NE(context, nullptr); @@ -402,16 +411,23 @@ TEST(TestExclusionDataIntegration, UnknownDataTypeOnExclusionData) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestExclusionDataIntegration, ExcludeInputByClientIP) { - auto rule = read_file("exclude_one_input_by_ip.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder builder = ddwaf_builder_init(nullptr); - ddwaf_handle handle1 = ddwaf_init(&rule, nullptr, nullptr); + { + auto rule = read_file("exclude_one_input_by_ip.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { ddwaf_context context = ddwaf_context_init(handle1); @@ -449,16 +465,16 @@ TEST(TestExclusionDataIntegration, ExcludeInputByClientIP) ddwaf_context_destroy(context); } - ddwaf_handle handle2; { - auto root = yaml_to_object( + auto data = yaml_to_object( R"({exclusion_data: [{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.0.1, expiration: 0}]}]})"); - - handle2 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle2, nullptr); - ddwaf_object_free(&root); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusion_data"), &data, nullptr); + ddwaf_object_free(&data); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context = ddwaf_context_init(handle2); ASSERT_NE(context, nullptr); @@ -485,14 +501,9 @@ TEST(TestExclusionDataIntegration, ExcludeInputByClientIP) ddwaf_context_destroy(context); } - ddwaf_handle handle3; - { - auto root = yaml_to_object(R"({exclusion_data: []})"); - - handle3 = ddwaf_update(handle1, &root, nullptr); - ASSERT_NE(handle3, nullptr); - ddwaf_object_free(&root); - } + ddwaf_builder_remove_config(builder, LSTRARG("exclusion_data")); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { ddwaf_context context = ddwaf_context_init(handle3); @@ -532,6 +543,8 @@ TEST(TestExclusionDataIntegration, ExcludeInputByClientIP) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } } // namespace diff --git a/tests/integration/interface/builder/test.cpp b/tests/integration/interface/builder/test.cpp new file mode 100644 index 000000000..ce1c101bc --- /dev/null +++ b/tests/integration/interface/builder/test.cpp @@ -0,0 +1,172 @@ +// 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 "common/gtest_utils.hpp" +#include "ddwaf.h" +#include "version.hpp" + +using namespace ddwaf; + +namespace { + +constexpr std::string_view base_dir = "integration/interface/builder/"; + +TEST(TestEngineBuilderFunctional, EmptyConfig) +{ + auto config = yaml_to_object("{}"); + ASSERT_NE(config.type, DDWAF_OBJ_INVALID); + + ddwaf_builder builder = ddwaf_builder_init(nullptr); + ASSERT_NE(builder, nullptr); + + ddwaf_builder_add_or_update_config(builder, LSTRARG("default"), &config, nullptr); + ddwaf_object_free(&config); + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); + ASSERT_EQ(handle, nullptr); + + ddwaf_builder_destroy(builder); +} + +TEST(TestEngineBuilderFunctional, BaseRules) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + ASSERT_NE(builder, nullptr); + + // Add the first config + { + auto config = read_file("base_rules_1.yaml", base_dir); + ASSERT_NE(config.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &config, nullptr); + ddwaf_object_free(&config); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + + // Test that the rules work + { + ddwaf_context context = ddwaf_context_init(handle); + + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_context_destroy(context); + } + + // Update the config + { + ddwaf_destroy(handle); + auto config = read_file("base_rules_2.yaml", base_dir); + ASSERT_NE(config.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &config, nullptr); + ddwaf_object_free(&config); + } + + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + + // Test that the old rules don't work and new ones do + { + ddwaf_context context = ddwaf_context_init(handle); + + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "value2", ddwaf_object_string(&tmp, "rule2")); + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_context_destroy(context); + } + + // Remove the rules + ddwaf_destroy(handle); + ddwaf_builder_remove_config(builder, LSTRARG("rules")); + handle = ddwaf_builder_build_instance(builder); + ASSERT_EQ(handle, nullptr); + + ddwaf_builder_destroy(builder); +} + +TEST(TestEngineBuilderFunctional, CustomRules) +{ + ddwaf_builder builder = ddwaf_builder_init(nullptr); + ASSERT_NE(builder, nullptr); + + // Add the first config + { + auto config = read_file("custom_rules_1.yaml", base_dir); + ASSERT_NE(config.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &config, nullptr); + ddwaf_object_free(&config); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + + // Test that the rules work + { + ddwaf_context context = ddwaf_context_init(handle); + + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_context_destroy(context); + } + + // Update the config + { + ddwaf_destroy(handle); + auto config = read_file("custom_rules_2.yaml", base_dir); + ASSERT_NE(config.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &config, nullptr); + ddwaf_object_free(&config); + } + + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + + // Test that the old rules don't work and new ones do + { + ddwaf_context context = ddwaf_context_init(handle); + + ddwaf_object tmp; + ddwaf_object root; + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "value1", ddwaf_object_string(&tmp, "rule1")); + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_OK); + + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "value2", ddwaf_object_string(&tmp, "rule2")); + EXPECT_EQ(ddwaf_run(context, &root, nullptr, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_context_destroy(context); + } + + // Remove the rules + ddwaf_destroy(handle); + ddwaf_builder_remove_config(builder, LSTRARG("rules")); + handle = ddwaf_builder_build_instance(builder); + ASSERT_EQ(handle, nullptr); + + ddwaf_builder_destroy(builder); +} + +} // namespace diff --git a/tests/integration/interface/builder/yaml/base_rules_1.yaml b/tests/integration/interface/builder/yaml/base_rules_1.yaml new file mode 100644 index 000000000..c7d617b1a --- /dev/null +++ b/tests/integration/interface/builder/yaml/base_rules_1.yaml @@ -0,0 +1,13 @@ +version: '2.1' +rules: + - id: 1 + name: rule1 + tags: + type: flow1 + category: category1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: rule1 diff --git a/tests/integration/interface/builder/yaml/base_rules_2.yaml b/tests/integration/interface/builder/yaml/base_rules_2.yaml new file mode 100644 index 000000000..71b4ff547 --- /dev/null +++ b/tests/integration/interface/builder/yaml/base_rules_2.yaml @@ -0,0 +1,15 @@ +version: '2.1' +rules: + - id: 2 + name: rule2 + tags: + type: flow2 + category: category2 + confidence: 1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: rule2 + diff --git a/tests/integration/interface/builder/yaml/custom_rules_1.yaml b/tests/integration/interface/builder/yaml/custom_rules_1.yaml new file mode 100644 index 000000000..c7d617b1a --- /dev/null +++ b/tests/integration/interface/builder/yaml/custom_rules_1.yaml @@ -0,0 +1,13 @@ +version: '2.1' +rules: + - id: 1 + name: rule1 + tags: + type: flow1 + category: category1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: rule1 diff --git a/tests/integration/interface/builder/yaml/custom_rules_2.yaml b/tests/integration/interface/builder/yaml/custom_rules_2.yaml new file mode 100644 index 000000000..71b4ff547 --- /dev/null +++ b/tests/integration/interface/builder/yaml/custom_rules_2.yaml @@ -0,0 +1,15 @@ +version: '2.1' +rules: + - id: 2 + name: rule2 + tags: + type: flow2 + category: category2 + confidence: 1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: rule2 + diff --git a/tests/integration/interface/waf/test.cpp b/tests/integration/interface/waf/test.cpp index c0f18eb68..0ec611f8e 100644 --- a/tests/integration/interface/waf/test.cpp +++ b/tests/integration/interface/waf/test.cpp @@ -5,6 +5,7 @@ // Copyright 2021 Datadog, Inc. #include "common/gtest_utils.hpp" +#include "ddwaf.h" #include "version.hpp" using namespace ddwaf; @@ -23,7 +24,7 @@ TEST(TestWafIntegration, Empty) ddwaf_object_free(&rule); } -TEST(TestWafIntegration, ddwaf_get_version) +TEST(TestWafIntegration, GetWafVersion) { EXPECT_STREQ(ddwaf_get_version(), ddwaf::current_version.cstring()); } @@ -192,51 +193,25 @@ TEST(TestWafIntegration, InvalidVersionNoRules) ddwaf_object_free(&rule); } -TEST(TestWafIntegration, UpdateWithNullObject) -{ - EXPECT_EQ(ddwaf_update(nullptr, nullptr, nullptr), nullptr); -} - -TEST(TestWafIntegration, UpdateWithNullHandle) -{ - auto rule = read_file("rule_data.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - EXPECT_EQ(ddwaf_update(handle, nullptr, nullptr), nullptr); - ddwaf_destroy(handle); -} - -TEST(TestWafIntegration, UpdateEmpty) +TEST(TestWafIntegration, PreloadRuleData) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(nullptr); + ASSERT_NE(builder, nullptr); - ddwaf_handle handle = ddwaf_init(&rule, &config, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - rule = yaml_to_object("{}"); - ddwaf_handle new_handle = ddwaf_update(handle, &rule, nullptr); - ASSERT_EQ(new_handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf_destroy(handle); -} + { + auto rule = read_file("rules_requiring_data.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("default"), &rule, nullptr); + ddwaf_object_free(&rule); -TEST(TestWafIntegration, PreloadRuleData) -{ - auto rule = read_file("rule_data_with_data.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + auto rule_data = read_file("rule_data.yaml", base_dir); + ASSERT_TRUE(rule_data.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rule_data"), &rule_data, nullptr); + ddwaf_object_free(&rule_data); + } - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); { ddwaf_context context = ddwaf_context_init(handle); @@ -267,12 +242,13 @@ TEST(TestWafIntegration, PreloadRuleData) } { - auto root = yaml_to_object( + auto rule_data = yaml_to_object( R"({rules_data: [{id: usr_data, type: data_with_expiration, data: [{value: pepe, expiration: 0}]}, {id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.2, expiration: 0}]}]})"); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rule_data"), &rule_data, nullptr); + ddwaf_object_free(&rule_data); - ddwaf_handle new_handle = ddwaf_update(handle, &root, nullptr); + ddwaf_handle new_handle = ddwaf_builder_build_instance(builder); ASSERT_NE(new_handle, nullptr); - ddwaf_object_free(&root); ddwaf_destroy(handle); handle = new_handle; @@ -307,6 +283,7 @@ TEST(TestWafIntegration, PreloadRuleData) } ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateRules) @@ -316,15 +293,22 @@ TEST(TestWafIntegration, UpdateRules) ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; - ddwaf_handle handle = ddwaf_init(&rule, &config, nullptr); + ddwaf_builder builder = ddwaf_builder_init(&config); + ddwaf_builder_add_or_update_config(builder, "default", sizeof("default") - 1, &rule, nullptr); + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); ddwaf_context context1 = ddwaf_context_init(handle); ASSERT_NE(context1, nullptr); + ddwaf_builder_remove_config(builder, "default", sizeof("default") - 1); + rule = read_file("interface3.yaml", base_dir); - ddwaf_handle new_handle = ddwaf_update(handle, &rule, nullptr); + ddwaf_builder_add_or_update_config( + builder, "new_config", sizeof("new_config") - 1, &rule, nullptr); + ddwaf_handle new_handle = ddwaf_builder_build_instance(builder); ASSERT_NE(new_handle, nullptr); ddwaf_object_free(&rule); @@ -353,48 +337,38 @@ TEST(TestWafIntegration, UpdateRules) ddwaf_context_destroy(context2); ddwaf_context_destroy(context1); -} -TEST(TestWafIntegration, UpdateInvalidRules) -{ - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; - - ddwaf_handle handle = ddwaf_init(&rule, &config, nullptr); - ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); - - rule = yaml_to_object("{rules: []}"); - ddwaf_handle new_handle = ddwaf_update(handle, &rule, nullptr); - ASSERT_EQ(new_handle, nullptr); - ddwaf_object_free(&rule); - - ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateDisableEnableRuleByID) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); + ASSERT_NE(builder, nullptr); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("interface"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); - ddwaf_handle handle2; { auto overrides = yaml_to_object(R"({rules_override: [{rules_target: [{rule_id: 1}], enabled: false}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); ddwaf_context context2 = ddwaf_context_init(handle2); ASSERT_NE(context2, nullptr); @@ -418,12 +392,9 @@ TEST(TestWafIntegration, UpdateDisableEnableRuleByID) ddwaf_context_destroy(context1); ddwaf_destroy(handle1); - ddwaf_handle handle3; - { - auto overrides = yaml_to_object(R"({rules_override: []})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); - ddwaf_object_free(&overrides); - } + ddwaf_builder_remove_config(builder, LSTRARG("overrides")); + auto *handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); ddwaf_context context3 = ddwaf_context_init(handle3); ASSERT_NE(context3, nullptr); @@ -443,29 +414,37 @@ TEST(TestWafIntegration, UpdateDisableEnableRuleByID) ddwaf_context_destroy(context3); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateDisableEnableRuleByTags) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("default"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); - ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {type: flow2}}], enabled: false}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); ddwaf_context context2 = ddwaf_context_init(handle2); ASSERT_NE(context2, nullptr); @@ -492,12 +471,9 @@ TEST(TestWafIntegration, UpdateDisableEnableRuleByTags) ddwaf_context_destroy(context2); ddwaf_destroy(handle1); - ddwaf_handle handle3; - { - auto overrides = yaml_to_object(R"({rules_override: []})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); - ddwaf_object_free(&overrides); - } + ddwaf_builder_remove_config(builder, LSTRARG("overrides")); + auto *handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); context2 = ddwaf_context_init(handle2); ASSERT_NE(context2, nullptr); @@ -527,18 +503,25 @@ TEST(TestWafIntegration, UpdateDisableEnableRuleByTags) ddwaf_context_destroy(context3); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateActionsByID) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); + ASSERT_NE(builder, nullptr); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { uint32_t actions_size; @@ -551,9 +534,11 @@ TEST(TestWafIntegration, UpdateActionsByID) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle2 = ddwaf_builder_build_instance(builder); + uint32_t actions_size; const char *const *actions = ddwaf_known_actions(handle2, &actions_size); EXPECT_EQ(actions_size, 1); @@ -626,9 +611,11 @@ TEST(TestWafIntegration, UpdateActionsByID) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [redirect]}], actions: [{id: redirect, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle3 = ddwaf_builder_build_instance(builder); + uint32_t actions_size; const char *const *actions = ddwaf_known_actions(handle3, &actions_size); EXPECT_EQ(actions_size, 1); @@ -670,18 +657,24 @@ TEST(TestWafIntegration, UpdateActionsByID) ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateActionsByTags) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { uint32_t actions_size; @@ -694,9 +687,11 @@ TEST(TestWafIntegration, UpdateActionsByTags) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], on_match: [block]}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle2 = ddwaf_builder_build_instance(builder); + uint32_t actions_size; const char *const *actions = ddwaf_known_actions(handle2, &actions_size); EXPECT_EQ(actions_size, 1); @@ -766,27 +761,35 @@ TEST(TestWafIntegration, UpdateActionsByTags) ddwaf_destroy(handle1); ddwaf_destroy(handle2); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateTagsByID) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], tags: {category: new_category, confidence: 0, new_tag: value}}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -834,12 +837,8 @@ TEST(TestWafIntegration, UpdateTagsByID) ddwaf_context_destroy(context1); } - ddwaf_handle handle3; - { - auto overrides = yaml_to_object(R"({rules_override: []})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); - ddwaf_object_free(&overrides); - } + ddwaf_builder_remove_config(builder, LSTRARG("overrides")); + auto *handle3 = ddwaf_builder_build_instance(builder); { ddwaf_context context3 = ddwaf_context_init(handle3); @@ -891,27 +890,36 @@ TEST(TestWafIntegration, UpdateTagsByID) ddwaf_destroy(handle2); ddwaf_destroy(handle1); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateTagsByTags) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], tags: {new_tag: value, confidence: 0}}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -1052,13 +1060,8 @@ TEST(TestWafIntegration, UpdateTagsByTags) ddwaf_context_destroy(context1); } - ddwaf_handle handle3; - { - auto overrides = yaml_to_object( - R"({rules_override: [{rules_target: [{tags: {confidence: 0}}], tags: {should_not: exist}}]})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); - ddwaf_object_free(&overrides); - } + ddwaf_builder_remove_config(builder, LSTRARG("overrides")); + auto *handle3 = ddwaf_builder_build_instance(builder); { ddwaf_context context3 = ddwaf_context_init(handle3); @@ -1109,27 +1112,36 @@ TEST(TestWafIntegration, UpdateTagsByTags) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateOverrideByIDAndTag) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {type: flow1}}], tags: {new_tag: old_value}, on_match: ["block"], enabled: false}, {rules_target: [{rule_id: 1}], tags: {new_tag: new_value}, enabled: true}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -1183,14 +1195,17 @@ TEST(TestWafIntegration, UpdateOverrideByIDAndTag) ddwaf_context_destroy(context2); } - ddwaf_handle handle3; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {type: flow1}}], on_match: ["block"]}, {rules_target: [{rule_id: 1}], on_match: []}]})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); + { ddwaf_context context2 = ddwaf_context_init(handle2); ASSERT_NE(context2, nullptr); @@ -1244,14 +1259,17 @@ TEST(TestWafIntegration, UpdateOverrideByIDAndTag) ddwaf_context_destroy(context3); } - ddwaf_handle handle4; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {type: flow1}}], enabled: true}, {rules_target: [{rule_id: 1}], enabled: false}]})"); - handle4 = ddwaf_update(handle3, &overrides, nullptr); + ASSERT_TRUE(overrides.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle4 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle4, nullptr); + { ddwaf_context context3 = ddwaf_context_init(handle3); ASSERT_NE(context3, nullptr); @@ -1276,55 +1294,71 @@ TEST(TestWafIntegration, UpdateOverrideByIDAndTag) ddwaf_destroy(handle2); ddwaf_destroy(handle3); ddwaf_destroy(handle4); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateInvalidOverrides) { + ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); + auto rule = read_file("interface.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; - - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); auto overrides = yaml_to_object(R"({rules_override: [{enabled: false}]})"); - ddwaf_handle handle2 = ddwaf_update(handle1, &overrides, nullptr); - ASSERT_NE(handle2, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + ddwaf_destroy(handle1); ddwaf_destroy(handle2); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateRuleData) { - auto rule = read_file("rule_data.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("rules_requiring_data.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { - auto data = yaml_to_object( + auto rule_data = yaml_to_object( 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_builder_add_or_update_config(builder, LSTRARG("rule_data"), &rule_data, nullptr); + ddwaf_object_free(&rule_data); } - ddwaf_handle handle3; + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { - auto data = yaml_to_object( + auto rule_data = yaml_to_object( 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_builder_add_or_update_config(builder, LSTRARG("rule_data"), &rule_data, nullptr); + ddwaf_object_free(&rule_data); } + auto *handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); + ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -1365,27 +1399,35 @@ TEST(TestWafIntegration, UpdateRuleData) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateAndRevertRuleData) { - auto rule = read_file("rule_data.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("rules_requiring_data.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { - auto data = yaml_to_object( + auto rule_data = yaml_to_object( 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_builder_add_or_update_config(builder, LSTRARG("rule_data"), &rule_data, nullptr); + ddwaf_object_free(&rule_data); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + ddwaf_object tmp; { ddwaf_context context1 = ddwaf_context_init(handle1); @@ -1407,12 +1449,9 @@ TEST(TestWafIntegration, UpdateAndRevertRuleData) ddwaf_context_destroy(context2); } - ddwaf_handle handle3; - { - auto data = yaml_to_object(R"({rules_data: []})"); - handle3 = ddwaf_update(handle2, &data, nullptr); - ddwaf_object_free(&data); - } + ddwaf_builder_remove_config(builder, LSTRARG("rule_data")); + auto *handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { ddwaf_context context2 = ddwaf_context_init(handle2); @@ -1437,49 +1476,36 @@ TEST(TestWafIntegration, UpdateAndRevertRuleData) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); -} - -TEST(TestWafIntegration, UpdateInvalidRuleData) -{ - auto rule = read_file("rule_data.yaml", base_dir); - 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 = yaml_to_object( - 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); + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateRuleExclusions) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { auto exclusions = yaml_to_object(R"({exclusions: [{id: 1, rules_target: [{rule_id: 1}]}]})"); - handle2 = ddwaf_update(handle1, &exclusions, nullptr); + ASSERT_NE(exclusions.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusions"), &exclusions, nullptr); ddwaf_object_free(&exclusions); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -1521,12 +1547,9 @@ TEST(TestWafIntegration, UpdateRuleExclusions) } ddwaf_destroy(handle1); - ddwaf_handle handle3; - { - auto exclusions = yaml_to_object(R"({exclusions: []})"); - handle3 = ddwaf_update(handle2, &exclusions, nullptr); - ddwaf_object_free(&exclusions); - } + ddwaf_builder_remove_config(builder, LSTRARG("exclusions")); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { ddwaf_context context2 = ddwaf_context_init(handle2); @@ -1550,26 +1573,35 @@ TEST(TestWafIntegration, UpdateRuleExclusions) ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateInputExclusions) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { auto exclusions = yaml_to_object(R"({exclusions: [{id: 1, inputs: [{address: value1}]}]})"); - handle2 = ddwaf_update(handle1, &exclusions, nullptr); + ASSERT_NE(exclusions.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusions"), &exclusions, nullptr); ddwaf_object_free(&exclusions); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -1631,12 +1663,9 @@ TEST(TestWafIntegration, UpdateInputExclusions) } ddwaf_destroy(handle1); - ddwaf_handle handle3; - { - auto exclusions = yaml_to_object(R"({exclusions: []})"); - handle3 = ddwaf_update(handle2, &exclusions, nullptr); - ddwaf_object_free(&exclusions); - } + ddwaf_builder_remove_config(builder, LSTRARG("exclusions")); + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { ddwaf_context context2 = ddwaf_context_init(handle2); @@ -1660,29 +1689,38 @@ TEST(TestWafIntegration, UpdateInputExclusions) ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, UpdateEverything) { - auto rule = read_file("interface_with_data.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface_with_data.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + ddwaf_handle handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); // After this update: // - No rule will match server.request.query - ddwaf_handle handle2; { auto exclusions = yaml_to_object(R"({exclusions: [{id: 1, inputs: [{address: server.request.query}]}]})"); - handle2 = ddwaf_update(handle1, &exclusions, nullptr); + ASSERT_NE(exclusions.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusions"), &exclusions, nullptr); ddwaf_object_free(&exclusions); } + ddwaf_handle handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); + { ddwaf_context context1 = ddwaf_context_init(handle1); ASSERT_NE(context1, nullptr); @@ -1728,14 +1766,17 @@ TEST(TestWafIntegration, UpdateEverything) // After this update: // - No rule will match server.request.query // - Rules with confidence=1 will provide a block action - ddwaf_handle handle3; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{tags: {confidence: 1}}], on_match: [block]}]})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); } + ddwaf_handle handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); + { ddwaf_context context2 = ddwaf_context_init(handle2); ASSERT_NE(context2, nullptr); @@ -1793,14 +1834,15 @@ TEST(TestWafIntegration, UpdateEverything) // - 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 = yaml_to_object( 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); + ASSERT_NE(data.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rule_data"), &data, nullptr); ddwaf_object_free(&data); } + ddwaf_handle handle4 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle4, nullptr); { @@ -1856,13 +1898,14 @@ TEST(TestWafIntegration, UpdateEverything) // - 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 = read_file("rule_data.yaml", base_dir); - handle5 = ddwaf_update(handle4, &data, nullptr); - ddwaf_object_free(&data); + auto rule = read_file("rules_requiring_data.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); } + ddwaf_handle handle5 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle5, nullptr); { @@ -1956,13 +1999,14 @@ TEST(TestWafIntegration, UpdateEverything) // - 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 = read_file("interface_with_data.yaml", base_dir); - handle6 = ddwaf_update(handle5, &data, nullptr); - ddwaf_object_free(&data); + auto rule = read_file("interface_with_data.yaml", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); } + ddwaf_handle handle6 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle6, nullptr); { @@ -2050,13 +2094,8 @@ TEST(TestWafIntegration, UpdateEverything) // 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 = yaml_to_object(R"({exclusions: []})"); - handle7 = ddwaf_update(handle6, &exclusions, nullptr); - ddwaf_object_free(&exclusions); - } - + ddwaf_builder_remove_config(builder, LSTRARG("exclusions")); + ddwaf_handle handle7 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle7, nullptr); { @@ -2093,12 +2132,9 @@ TEST(TestWafIntegration, UpdateEverything) // After this update: // - Rules with ip_data or usr_data will now match - ddwaf_handle handle8; - { - auto exclusions = yaml_to_object(R"({rules_override: []})"); - handle8 = ddwaf_update(handle7, &exclusions, nullptr); - ddwaf_object_free(&exclusions); - } + ddwaf_builder_remove_config(builder, LSTRARG("overrides")); + ddwaf_handle handle8 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle8, nullptr); ASSERT_NE(handle8, nullptr); @@ -2167,13 +2203,8 @@ TEST(TestWafIntegration, UpdateEverything) } // After this update, back to the original behaviour - ddwaf_handle handle9; - { - auto exclusions = yaml_to_object(R"({rules_data: []})"); - handle9 = ddwaf_update(handle8, &exclusions, nullptr); - ddwaf_object_free(&exclusions); - } - + ddwaf_builder_remove_config(builder, LSTRARG("rule_data")); + ddwaf_handle handle9 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle9, nullptr); { @@ -2270,34 +2301,41 @@ TEST(TestWafIntegration, UpdateEverything) ddwaf_destroy(handle3); ddwaf_destroy(handle2); ddwaf_destroy(handle1); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, KnownAddressesDisabledRule) { - auto rule = read_file("ruleset_with_disabled_rule.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("ruleset_with_disabled_rule.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("ruleset"), &rule, nullptr); + ddwaf_object_free(&rule); + } + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); - ddwaf_handle handle2; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: id-rule-1}], enabled: true}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("override"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle2 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle2, nullptr); - ddwaf_handle handle3; { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: id-rule-1}], enabled: false}]})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); + ddwaf_builder_add_or_update_config(builder, LSTRARG("override"), &overrides, nullptr); ddwaf_object_free(&overrides); } + auto *handle3 = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle3, nullptr); { uint32_t size; @@ -2330,18 +2368,24 @@ TEST(TestWafIntegration, KnownAddressesDisabledRule) ddwaf_destroy(handle1); ddwaf_destroy(handle2); ddwaf_destroy(handle3); + + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, KnownActions) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_config config{{0, 0, 0}, {nullptr, nullptr}, nullptr}; + ddwaf_builder builder = ddwaf_builder_init(&config); - ddwaf_handle handle1 = ddwaf_init(&rule, &config, nullptr); + { + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + } + + auto *handle1 = ddwaf_builder_build_instance(builder); ASSERT_NE(handle1, nullptr); - ddwaf_object_free(&rule); { uint32_t size; @@ -2355,9 +2399,12 @@ TEST(TestWafIntegration, KnownActions) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}]})"); - handle2 = ddwaf_update(handle1, &overrides, nullptr); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle2 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle2, &size); EXPECT_EQ(size, 1); @@ -2376,9 +2423,12 @@ TEST(TestWafIntegration, KnownActions) { auto overrides = yaml_to_object(R"({rules_override: [{rules_target: [{rule_id: 1}], enabled: false}]})"); - handle3 = ddwaf_update(handle2, &overrides, nullptr); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle3 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle3, &size); EXPECT_EQ(size, 0); @@ -2390,11 +2440,20 @@ TEST(TestWafIntegration, KnownActions) // Add a new action type and update another rule to use it ddwaf_handle handle4; { + auto action_cfg = yaml_to_object( + R"({actions: [{id: redirect, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); + ASSERT_NE(action_cfg.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("actions"), &action_cfg, nullptr); + ddwaf_object_free(&action_cfg); + auto overrides = yaml_to_object( - R"({rules_override: [{rules_target: [{rule_id: 2}], on_match: [redirect]}], actions: [{id: redirect, type: redirect_request, parameters: {location: http://google.com, status_code: 303}}]})"); - handle4 = ddwaf_update(handle3, &overrides, nullptr); + R"({rules_override: [{rules_target: [{rule_id: 2}], on_match: [redirect]}]})"); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle4 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle4, &size); EXPECT_EQ(size, 1); @@ -2413,9 +2472,12 @@ TEST(TestWafIntegration, KnownActions) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}, {rules_target: [{rule_id: 2}], on_match: [redirect]}]})"); - handle5 = ddwaf_update(handle4, &overrides, nullptr); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle5 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle5, &size); EXPECT_EQ(size, 2); @@ -2434,9 +2496,12 @@ TEST(TestWafIntegration, KnownActions) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [block]}, {rules_target: [{rule_id: 2}], on_match: [redirect]}, {rules_target: [{rule_id: 3}], on_match: [block, stack_trace]}]})"); - handle6 = ddwaf_update(handle5, &overrides, nullptr); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle6 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle6, &size); EXPECT_EQ(size, 3); @@ -2455,10 +2520,19 @@ TEST(TestWafIntegration, KnownActions) ddwaf_handle handle7; { auto overrides = yaml_to_object( - R"({exclusions: [{id: 1, rules_target: [{rule_id: 1}], on_match: block}], rules_override: [{rules_target: [{rule_id: 2}], on_match: [redirect]}, {rules_target: [{rule_id: 3}], on_match: [block, stack_trace]}]})"); - handle7 = ddwaf_update(handle6, &overrides, nullptr); + R"({rules_override: [{rules_target: [{rule_id: 2}], on_match: [redirect]}, {rules_target: [{rule_id: 3}], on_match: [block, stack_trace]}]})"); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + auto exclusions = yaml_to_object( + R"({exclusions: [{id: 1, rules_target: [{rule_id: 1}], on_match: block}]})"); + ASSERT_NE(exclusions.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("exclusions"), &exclusions, nullptr); + ddwaf_object_free(&exclusions); + + handle7 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle7, &size); EXPECT_EQ(size, 3); @@ -2476,9 +2550,9 @@ TEST(TestWafIntegration, KnownActions) // Remove actions from all other rules ddwaf_handle handle8; { - auto overrides = yaml_to_object(R"({rules_override: []})"); - handle8 = ddwaf_update(handle7, &overrides, nullptr); - ddwaf_object_free(&overrides); + ddwaf_builder_remove_config(builder, LSTRARG("overrides")); + + handle8 = ddwaf_builder_build_instance(builder); uint32_t size; const char *const *actions = ddwaf_known_actions(handle8, &size); @@ -2496,9 +2570,9 @@ TEST(TestWafIntegration, KnownActions) // Remove exclusions ddwaf_handle handle9; { - auto overrides = yaml_to_object(R"({exclusions: []})"); - handle9 = ddwaf_update(handle8, &overrides, nullptr); - ddwaf_object_free(&overrides); + ddwaf_builder_remove_config(builder, LSTRARG("exclusions")); + + handle9 = ddwaf_builder_build_instance(builder); uint32_t size; const char *const *actions = ddwaf_known_actions(handle9, &size); @@ -2513,9 +2587,12 @@ TEST(TestWafIntegration, KnownActions) { auto overrides = yaml_to_object( R"({rules_override: [{rules_target: [{rule_id: 1}], on_match: [whatever]}]})"); - handle10 = ddwaf_update(handle9, &overrides, nullptr); + ASSERT_NE(overrides.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("overrides"), &overrides, nullptr); ddwaf_object_free(&overrides); + handle10 = ddwaf_builder_build_instance(builder); + uint32_t size; const char *const *actions = ddwaf_known_actions(handle10, &size); EXPECT_EQ(size, 0); @@ -2525,6 +2602,7 @@ TEST(TestWafIntegration, KnownActions) } ddwaf_destroy(handle10); + ddwaf_builder_destroy(builder); } TEST(TestWafIntegration, KnownActionsNullHandle) diff --git a/tests/integration/interface/waf/yaml/rule_data.yaml b/tests/integration/interface/waf/yaml/rule_data.yaml index 8a424ef0a..ffd852f28 100644 --- a/tests/integration/interface/waf/yaml/rule_data.yaml +++ b/tests/integration/interface/waf/yaml/rule_data.yaml @@ -1,26 +1,11 @@ -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: 2 - name: rule2 - tags: - type: flow2 - category: category2 - confidence: 0 - conditions: - - operator: exact_match - parameters: - inputs: - - address: usr.id - data: usr_data +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: paco + expiration: 0 diff --git a/tests/integration/interface/waf/yaml/rule_data_with_data.yaml b/tests/integration/interface/waf/yaml/rules_requiring_data.yaml similarity index 66% rename from tests/integration/interface/waf/yaml/rule_data_with_data.yaml rename to tests/integration/interface/waf/yaml/rules_requiring_data.yaml index 667167e94..8a424ef0a 100644 --- a/tests/integration/interface/waf/yaml/rule_data_with_data.yaml +++ b/tests/integration/interface/waf/yaml/rules_requiring_data.yaml @@ -5,6 +5,7 @@ rules: tags: type: flow1 category: category1 + confidence: 1 conditions: - operator: ip_match parameters: @@ -16,20 +17,10 @@ rules: tags: type: flow2 category: category2 + confidence: 0 conditions: - operator: exact_match parameters: inputs: - address: usr.id data: usr_data -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: paco - expiration: 0 diff --git a/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_id.json b/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_id.json index 656f28aba..4daeb3b74 100644 --- a/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_id.json +++ b/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_id.json @@ -68,31 +68,5 @@ "evaluate": false, "output": true } - ], - "scanners": [ - { - "id": "scanner-001", - "key": { - "operator": "match_regex", - "parameters": { - "regex": "(?:email|mail)", - "options": { - "case_sensitive": false, - "min_length": 5 - } - } - }, - "value": { - "operator": "match_regex", - "parameters": { - "regex": "\\b[\\w!#$%&'*+\\/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+\\/=?`{|}~^-]+)*(%40|@)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}\\b", - "options": { - "case_sensitive": false, - "min_length": 5 - } - } - }, - "tags": { "type": "email", "category": "pii" } - } ] } diff --git a/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_tags.json b/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_tags.json index 1633fa56f..cdd07a85d 100644 --- a/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_tags.json +++ b/tests/integration/processors/extract_schema/ruleset/processor_with_scanner_by_tags.json @@ -71,31 +71,5 @@ "evaluate": false, "output": true } - ], - "scanners": [ - { - "id": "scanner-001", - "key": { - "operator": "match_regex", - "parameters": { - "regex": "(?:email|mail)", - "options": { - "case_sensitive": false, - "min_length": 5 - } - } - }, - "value": { - "operator": "match_regex", - "parameters": { - "regex": "\\b[\\w!#$%&'*+\\/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+\\/=?`{|}~^-]+)*(%40|@)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}\\b", - "options": { - "case_sensitive": false, - "min_length": 5 - } - } - }, - "tags": { "type": "email", "category": "pii" } - } ] } diff --git a/tests/integration/processors/extract_schema/ruleset/scanners.json b/tests/integration/processors/extract_schema/ruleset/scanners.json new file mode 100644 index 000000000..5c27131c1 --- /dev/null +++ b/tests/integration/processors/extract_schema/ruleset/scanners.json @@ -0,0 +1,28 @@ +{ + "scanners": [ + { + "id": "scanner-001", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "(?:email|mail)", + "options": { + "case_sensitive": false, + "min_length": 5 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b[\\w!#$%&'*+\\/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+\\/=?`{|}~^-]+)*(%40|@)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}\\b", + "options": { + "case_sensitive": false, + "min_length": 5 + } + } + }, + "tags": { "type": "email", "category": "pii" } + } + ] +} diff --git a/tests/integration/processors/extract_schema/test.cpp b/tests/integration/processors/extract_schema/test.cpp index 5e0576497..14d366107 100644 --- a/tests/integration/processors/extract_schema/test.cpp +++ b/tests/integration/processors/extract_schema/test.cpp @@ -14,7 +14,7 @@ constexpr std::string_view base_dir = "integration/processors/extract_schema"; TEST(TestExtractSchemaIntegration, Postprocessor) { auto rule = read_json_file("postprocessor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); @@ -58,7 +58,7 @@ TEST(TestExtractSchemaIntegration, Postprocessor) TEST(TestExtractSchemaIntegration, Preprocessor) { auto rule = read_json_file("preprocessor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); @@ -111,7 +111,7 @@ TEST(TestExtractSchemaIntegration, Preprocessor) TEST(TestExtractSchemaIntegration, Processor) { auto rule = read_json_file("processor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); @@ -168,12 +168,21 @@ TEST(TestExtractSchemaIntegration, Processor) TEST(TestExtractSchemaIntegration, ProcessorWithScannerByTags) { + ddwaf_builder builder = ddwaf_builder_init(nullptr); + auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); ddwaf_object_free(&rule); + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + ddwaf_object value; ddwaf_object map = DDWAF_OBJECT_MAP; ddwaf_object values = DDWAF_OBJECT_MAP; @@ -201,16 +210,27 @@ TEST(TestExtractSchemaIntegration, ProcessorWithScannerByTags) ddwaf_result_free(&out); ddwaf_context_destroy(context); ddwaf_destroy(handle); + + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, ProcessorWithScannerByID) { + ddwaf_builder builder = ddwaf_builder_init(nullptr); + auto rule = read_json_file("processor_with_scanner_by_id.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); - ASSERT_NE(handle, nullptr); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); ddwaf_object_free(&rule); + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + ddwaf_object value; ddwaf_object map = DDWAF_OBJECT_MAP; ddwaf_object values = DDWAF_OBJECT_MAP; @@ -239,15 +259,27 @@ TEST(TestExtractSchemaIntegration, ProcessorWithScannerByID) ddwaf_context_destroy(context); ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, ProcessorUpdate) { - auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + { + auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object value; @@ -280,14 +312,17 @@ TEST(TestExtractSchemaIntegration, ProcessorUpdate) } { - auto new_ruleset = read_json_file("postprocessor.json", base_dir); - auto *new_handle = ddwaf_update(handle, &new_ruleset, nullptr); - ddwaf_object_free(&new_ruleset); ddwaf_destroy(handle); - handle = new_handle; + auto rule = read_json_file("postprocessor.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); } + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + { ddwaf_object map = DDWAF_OBJECT_MAP; ddwaf_object values = DDWAF_OBJECT_MAP; @@ -316,15 +351,28 @@ TEST(TestExtractSchemaIntegration, ProcessorUpdate) } ddwaf_destroy(handle); + + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, ScannerUpdate) { - auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + { + auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object value; @@ -357,15 +405,18 @@ TEST(TestExtractSchemaIntegration, ScannerUpdate) } { - auto new_scanners = json_to_object( - R"({"scanners":[{"id":"scanner-002","value":{"operator":"match_regex","parameters":{"regex":"notanemail","options":{"case_sensitive":false,"min_length":1}}},"tags":{"type":"email","category":"pii"}}]})"); - auto *new_handle = ddwaf_update(handle, &new_scanners, nullptr); - ddwaf_object_free(&new_scanners); ddwaf_destroy(handle); - handle = new_handle; + auto scanner = json_to_object( + R"({"scanners":[{"id":"scanner-002","value":{"operator":"match_regex","parameters":{"regex":"notanemail","options":{"case_sensitive":false,"min_length":1}}},"tags":{"type":"email","category":"pii"}}]})"); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); } + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + { ddwaf_object map = DDWAF_OBJECT_MAP; ddwaf_object values = DDWAF_OBJECT_MAP; @@ -395,15 +446,28 @@ TEST(TestExtractSchemaIntegration, ScannerUpdate) } ddwaf_destroy(handle); + + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, ProcessorAndScannerUpdate) { - auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + { + auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object value; @@ -436,14 +500,17 @@ TEST(TestExtractSchemaIntegration, ProcessorAndScannerUpdate) } { - auto new_ruleset = read_json_file("processor_with_scanner_by_id.json", base_dir); - auto *new_handle = ddwaf_update(handle, &new_ruleset, nullptr); - ddwaf_object_free(&new_ruleset); ddwaf_destroy(handle); - handle = new_handle; + auto rule = read_json_file("processor_with_scanner_by_id.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); } + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + { ddwaf_object map = DDWAF_OBJECT_MAP; ddwaf_object values = DDWAF_OBJECT_MAP; @@ -473,15 +540,27 @@ TEST(TestExtractSchemaIntegration, ProcessorAndScannerUpdate) } ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, EmptyScannerUpdate) { - auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + { + auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object value; @@ -513,14 +592,10 @@ TEST(TestExtractSchemaIntegration, EmptyScannerUpdate) ddwaf_context_destroy(context); } - { - auto new_ruleset = json_to_object(R"({"scanners":[]})"); - auto *new_handle = ddwaf_update(handle, &new_ruleset, nullptr); - ddwaf_object_free(&new_ruleset); - ddwaf_destroy(handle); - - handle = new_handle; - } + ddwaf_destroy(handle); + ddwaf_builder_remove_config(builder, LSTRARG("scanners")); + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); { ddwaf_object map = DDWAF_OBJECT_MAP; @@ -550,15 +625,27 @@ TEST(TestExtractSchemaIntegration, EmptyScannerUpdate) } ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, EmptyProcessorUpdate) { - auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ddwaf_builder builder = ddwaf_builder_init(nullptr); + + { + auto rule = read_json_file("processor_with_scanner_by_tags.json", base_dir); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); + + auto scanner = read_json_file("scanners.json", base_dir); + ASSERT_NE(scanner.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("scanners"), &scanner, nullptr); + ddwaf_object_free(&scanner); + } + + ddwaf_handle handle = ddwaf_builder_build_instance(builder); ASSERT_NE(handle, nullptr); - ddwaf_object_free(&rule); ddwaf_object value; @@ -591,14 +678,17 @@ TEST(TestExtractSchemaIntegration, EmptyProcessorUpdate) } { - auto new_ruleset = json_to_object(R"({"processors":[]})"); - auto *new_handle = ddwaf_update(handle, &new_ruleset, nullptr); - ddwaf_object_free(&new_ruleset); ddwaf_destroy(handle); - - handle = new_handle; + auto rule = json_to_object( + R"({"version": "2.2", "metadata": {"rules_version": "1.8.0"}, "rules": [{"id": "rule1", "name": "rule1", "tags": {"type": "flow1", "category": "category1"}, "conditions": [{"parameters": {"inputs": [{"address": "server.request.body.schema"}], "value": 8, "type": "unsigned"}, "operator": "equals"}]}], "processors": []})"); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); + ddwaf_builder_add_or_update_config(builder, LSTRARG("rules"), &rule, nullptr); + ddwaf_object_free(&rule); } + handle = ddwaf_builder_build_instance(builder); + ASSERT_NE(handle, nullptr); + { ddwaf_object map = DDWAF_OBJECT_MAP; ddwaf_object values = DDWAF_OBJECT_MAP; @@ -625,12 +715,13 @@ TEST(TestExtractSchemaIntegration, EmptyProcessorUpdate) } ddwaf_destroy(handle); + ddwaf_builder_destroy(builder); } TEST(TestExtractSchemaIntegration, PostprocessorWithEphemeralMapping) { auto rule = read_json_file("postprocessor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); @@ -697,7 +788,7 @@ TEST(TestExtractSchemaIntegration, PostprocessorWithEphemeralMapping) TEST(TestExtractSchemaIntegration, PreprocessorWithEphemeralMapping) { auto rule = read_json_file("preprocessor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); @@ -780,7 +871,7 @@ TEST(TestExtractSchemaIntegration, PreprocessorWithEphemeralMapping) TEST(TestExtractSchemaIntegration, ProcessorEphemeralExpression) { auto rule = read_json_file("processor.json", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ASSERT_NE(rule.type, DDWAF_OBJ_INVALID); ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); ASSERT_NE(handle, nullptr); ddwaf_object_free(&rule); diff --git a/tests/unit/action_mapper_builder_test.cpp b/tests/unit/builder/action_mapper_builder_test.cpp similarity index 84% rename from tests/unit/action_mapper_builder_test.cpp rename to tests/unit/builder/action_mapper_builder_test.cpp index cdca8346b..50293b979 100644 --- a/tests/unit/action_mapper_builder_test.cpp +++ b/tests/unit/builder/action_mapper_builder_test.cpp @@ -7,6 +7,7 @@ #include #include "action_mapper.hpp" +#include "builder/action_mapper_builder.hpp" #include "common/gtest_utils.hpp" @@ -107,38 +108,6 @@ TEST(TestActionMapperBuilder, SetAction) } } -TEST(TestActionMapperBuilder, SetActionAlias) -{ - action_mapper_builder builder; - builder.alias_default_action_to("block", "redirect"); - - auto actions = builder.build(); - - EXPECT_TRUE(actions.contains("block")); - EXPECT_TRUE(actions.contains("stack_trace")); - EXPECT_TRUE(actions.contains("extract_schema")); - EXPECT_TRUE(actions.contains("monitor")); - - EXPECT_TRUE(actions.contains("redirect")); - - { - const auto &action = actions.at("redirect"); - EXPECT_EQ(action.type, action_type::block_request); - EXPECT_STR(action.type_str, "block_request"); - - EXPECT_EQ(action.parameters.size(), 3); - EXPECT_STRV(action.parameters.at("status_code"), "403"); - EXPECT_STRV(action.parameters.at("type"), "auto"); - EXPECT_STRV(action.parameters.at("grpc_status_code"), "10"); - } -} - -TEST(TestActionMapperBuilder, SetInvalidActionAlias) -{ - action_mapper_builder builder; - EXPECT_THROW(builder.alias_default_action_to("blorck", "redirect"), std::runtime_error); -} - TEST(TestActionMapperBuilder, OverrideDefaultAction) { action_mapper_builder builder; diff --git a/tests/unit/builder/rule_builder_test.cpp b/tests/unit/builder/rule_builder_test.cpp new file mode 100644 index 000000000..e891e7f45 --- /dev/null +++ b/tests/unit/builder/rule_builder_test.cpp @@ -0,0 +1,42 @@ +// 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 "builder/rule_builder.hpp" +#include "common/gtest_utils.hpp" +#include "configuration/common/configuration.hpp" +#include "matcher/ip_match.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestRuleBuilder, SimpleRule) +{ + test::expression_builder exp_builder(1); + exp_builder.start_condition(); + exp_builder.add_argument(); + exp_builder.add_target("http.client_ip"); + exp_builder.end_condition(std::vector{"192.168.0.1"}); + + rule_spec spec{true, core_rule::source_type::base, "Test rule", {{"type", "flow1"}}, + exp_builder.build(), {}}; + + rule_builder builder{"test", spec}; + + auto rule = builder.build({}); + EXPECT_NE(rule, nullptr); + + EXPECT_STRV(rule->get_id(), "test"); + EXPECT_TRUE(rule->is_enabled()); + EXPECT_STRV(rule->get_name(), "Test rule"); + EXPECT_TRUE(rule->get_actions().empty()); + EXPECT_EQ(rule->get_source(), core_rule::source_type::base); + EXPECT_EQ(rule->get_module(), rule_module_category::waf); + EXPECT_EQ(rule->get_verdict(), core_rule::verdict_type::monitor); +} + +} // namespace diff --git a/tests/unit/configuration/action_parser_test.cpp b/tests/unit/configuration/action_parser_test.cpp new file mode 100644 index 000000000..d3a202326 --- /dev/null +++ b/tests/unit/configuration/action_parser_test.cpp @@ -0,0 +1,1010 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/actions_parser.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/common/configuration_collector.hpp" +#include "fmt/core.h" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestActionParser, EmptyActions) +{ + auto object = yaml_to_object(R"([])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestActionParser, SingleAction) +{ + auto object = yaml_to_object(R"([{id: block_1, type: block_request, parameters: {}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block_1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(cfg.actions.size(), 1); + EXPECT_TRUE(cfg.actions.contains("block_1")); + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_TRUE(change.actions.contains("block_1")); + + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestActionParser, RedirectAction) +{ + std::vector> redirections{ + {"redirect_301", "301", "http://www.datadoghq.com"}, + {"redirect_302", "302", "http://www.datadoghq.com"}, + {"redirect_303", "303", "http://www.datadoghq.com"}, + {"redirect_307", "307", "http://www.datadoghq.com"}, + {"redirect_https", "303", "https://www.datadoghq.com"}, + {"redirect_path", "303", "/security/appsec"}, + }; + + std::string yaml; + yaml.append("["); + for (auto &[name, status_code, url] : redirections) { + yaml += fmt::format("{{id: {}, parameters: {{location: \"{}\", status_code: {}}}, type: " + "redirect_request}},", + name, url, status_code); + } + yaml.append("]"); + auto object = yaml_to_object(yaml); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 6); + EXPECT_NE(loaded.find("redirect_301"), loaded.end()); + EXPECT_NE(loaded.find("redirect_302"), loaded.end()); + EXPECT_NE(loaded.find("redirect_303"), loaded.end()); + EXPECT_NE(loaded.find("redirect_307"), loaded.end()); + EXPECT_NE(loaded.find("redirect_https"), loaded.end()); + EXPECT_NE(loaded.find("redirect_path"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 6); + EXPECT_EQ(cfg.actions.size(), 6); + + for (auto &[name, status_code, url] : redirections) { + EXPECT_TRUE(change.actions.contains(name)); + ASSERT_TRUE(cfg.actions.contains(name)); + + const auto &spec = cfg.actions[name]; + EXPECT_EQ(spec.type, action_type::redirect_request) << name; + EXPECT_EQ(spec.type_str, "redirect_request"); + EXPECT_EQ(spec.parameters.size(), 2); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), status_code.c_str()); + EXPECT_STR(parameters.at("location"), url.c_str()); + } + + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestActionParser, RedirectActionInvalidStatusCode) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 404}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("redirect")); + EXPECT_TRUE(cfg.actions.contains("redirect")); + + { + const auto &spec = cfg.actions["redirect"]; + EXPECT_EQ(spec.type, action_type::redirect_request); + EXPECT_EQ(spec.type_str, "redirect_request"); + EXPECT_EQ(spec.parameters.size(), 2); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "303"); + EXPECT_STR(parameters.at("location"), "http://www.google.com"); + } +} + +TEST(TestActionParser, RedirectActionInvalid300StatusCode) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 304}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("redirect")); + EXPECT_TRUE(cfg.actions.contains("redirect")); + + { + const auto &spec = cfg.actions["redirect"]; + EXPECT_EQ(spec.type, action_type::redirect_request); + EXPECT_EQ(spec.type_str, "redirect_request"); + EXPECT_EQ(spec.parameters.size(), 2); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "303"); + EXPECT_STR(parameters.at("location"), "http://www.google.com"); + } +} + +TEST(TestActionParser, RedirectActionMissingStatusCode) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {location: "http://www.google.com"}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("redirect")); + EXPECT_TRUE(cfg.actions.contains("redirect")); + + { + const auto &spec = cfg.actions["redirect"]; + EXPECT_EQ(spec.type, action_type::redirect_request); + EXPECT_EQ(spec.type_str, "redirect_request"); + EXPECT_EQ(spec.parameters.size(), 2); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "303"); + EXPECT_STR(parameters.at("location"), "http://www.google.com"); + } +} + +TEST(TestActionParser, RedirectActionMissingLocation) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {status_code: 303}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("redirect")); + EXPECT_TRUE(cfg.actions.contains("redirect")); + + { + const auto &spec = cfg.actions["redirect"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, RedirectActionNonHttpURL) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {status_code: 303, location: ftp://myftp.mydomain.com}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("redirect")); + EXPECT_TRUE(cfg.actions.contains("redirect")); + + { + const auto &spec = cfg.actions["redirect"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, RedirectActionInvalidRelativePathURL) +{ + auto object = yaml_to_object( + R"([{id: redirect, parameters: {status_code: 303, location: ../../../etc/passwd}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("redirect"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("redirect")); + EXPECT_TRUE(cfg.actions.contains("redirect")); + + { + const auto &spec = cfg.actions["redirect"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, OverrideDefaultBlockAction) +{ + auto object = yaml_to_object( + R"([{id: block, parameters: {location: "http://www.google.com", status_code: 302}, type: redirect_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("block")); + EXPECT_TRUE(cfg.actions.contains("block")); + + { + const auto &spec = cfg.actions["block"]; + EXPECT_EQ(spec.type, action_type::redirect_request); + EXPECT_EQ(spec.type_str, "redirect_request"); + EXPECT_EQ(spec.parameters.size(), 2); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "302"); + EXPECT_STR(parameters.at("location"), "http://www.google.com"); + } +} + +TEST(TestActionParser, BlockActionMissingStatusCode) +{ + auto object = yaml_to_object( + R"([{id: block, parameters: {type: "auto", grpc_status_code: 302}, type: block_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("block")); + EXPECT_TRUE(cfg.actions.contains("block")); + + { + const auto &spec = cfg.actions["block"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "302"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, UnknownActionType) +{ + auto object = yaml_to_object( + R"([{id: sanitize, parameters: {location: "http://www.google.com", status_code: 302}, type: new_action_type}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("sanitize"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("sanitize")); + EXPECT_TRUE(cfg.actions.contains("sanitize")); +} + +TEST(TestActionParser, BlockActionMissingGrpcStatusCode) +{ + auto object = yaml_to_object( + R"([{id: block, parameters: {type: "auto", status_code: 302}, type: block_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("block")); + EXPECT_TRUE(cfg.actions.contains("block")); + + { + const auto &spec = cfg.actions["block"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "302"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, BlockActionMissingType) +{ + auto object = yaml_to_object( + R"([{id: block, parameters: {grpc_status_code: 11, status_code: 302}, type: block_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("block")); + EXPECT_TRUE(cfg.actions.contains("block")); + + { + const auto &spec = cfg.actions["block"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "302"); + EXPECT_STR(parameters.at("grpc_status_code"), "11"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, BlockActionMissingParameters) +{ + auto object = yaml_to_object(R"([{id: block, parameters: {}, type: block_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("block")); + EXPECT_TRUE(cfg.actions.contains("block")); + + { + const auto &spec = cfg.actions["block"]; + EXPECT_EQ(spec.type, action_type::block_request); + EXPECT_EQ(spec.type_str, "block_request"); + EXPECT_EQ(spec.parameters.size(), 3); + + const auto ¶meters = spec.parameters; + EXPECT_STR(parameters.at("status_code"), "403"); + EXPECT_STR(parameters.at("grpc_status_code"), "10"); + EXPECT_STR(parameters.at("type"), "auto"); + } +} + +TEST(TestActionParser, MissingID) +{ + auto object = yaml_to_object( + R"([{parameters: {location: "http://www.google.com", status_code: 302}, type: new_action_type}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + EXPECT_EQ(change.actions.size(), 0); + EXPECT_EQ(cfg.actions.size(), 0); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } +} + +TEST(TestActionParser, MissingType) +{ + auto object = yaml_to_object( + R"([{id: sanitize, parameters: {location: "http://www.google.com", status_code: 302}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + EXPECT_EQ(change.actions.size(), 0); + EXPECT_EQ(cfg.actions.size(), 0); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("sanitize"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("sanitize"), error_rules.end()); + + ddwaf_object_free(&root); + } +} + +TEST(TestActionParser, MissingParameters) +{ + auto object = yaml_to_object(R"([{id: sanitize, type: sanitize_request}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + EXPECT_EQ(change.actions.size(), 0); + EXPECT_EQ(cfg.actions.size(), 0); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("sanitize"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("missing key 'parameters'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("sanitize"), error_rules.end()); + + ddwaf_object_free(&root); + } +} + +TEST(TestActionParser, DuplicateAction) +{ + auto object = yaml_to_object( + R"([{id: block_1, type: block_request, parameters: {}},{id: block_1, type: block_request, parameters: {}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto actions_array = static_cast(parameter(object)); + parse_actions(actions_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("block_1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("duplicate action"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("block_1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::actions); + EXPECT_EQ(change.actions.size(), 1); + EXPECT_EQ(cfg.actions.size(), 1); + + EXPECT_TRUE(change.actions.contains("block_1")); + EXPECT_TRUE(cfg.actions.contains("block_1")); +} + +} // namespace diff --git a/tests/unit/configuration/base_rule_parser_test.cpp b/tests/unit/configuration/base_rule_parser_test.cpp new file mode 100644 index 000000000..1c867fe77 --- /dev/null +++ b/tests/unit/configuration/base_rule_parser_test.cpp @@ -0,0 +1,944 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/rule_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestBaseRuleParser, ParseRule) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rules); + EXPECT_EQ(change.base_rules.size(), 1); + EXPECT_TRUE(change.base_rules.contains("1")); + + EXPECT_EQ(cfg.base_rules.size(), 1); + EXPECT_TRUE(cfg.base_rules.contains("1")); + + const rule_spec &rule = cfg.base_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, ParseRuleWithoutType) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, ParseRuleInvalidTransformer) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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], transformers: [unknown]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("invalid transformer unknown"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, ParseRuleWithoutID) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, ParseMultipleRules) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("1"), loaded.end()); + EXPECT_NE(loaded.find("secondrule"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rules); + EXPECT_EQ(change.base_rules.size(), 2); + EXPECT_TRUE(change.base_rules.contains("1")); + EXPECT_TRUE(change.base_rules.contains("secondrule")); + + EXPECT_EQ(cfg.base_rules.size(), 2); + EXPECT_TRUE(cfg.base_rules.contains("1")); + EXPECT_TRUE(cfg.base_rules.contains("secondrule")); + + { + const rule_spec &rule = cfg.base_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + } + + { + const rule_spec &rule = cfg.base_rules["secondrule"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->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.at("type"), "flow2"); + EXPECT_STR(rule.tags.at("category"), "category2"); + EXPECT_STR(rule.tags.at("confidence"), "none"); + } +} + +TEST(TestBaseRuleParser, ParseMultipleRulesOneInvalid) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("1"), loaded.end()); + EXPECT_NE(loaded.find("secondrule"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("error"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'conditions'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("error"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rules); + EXPECT_EQ(change.base_rules.size(), 2); + EXPECT_TRUE(change.base_rules.contains("1")); + EXPECT_TRUE(change.base_rules.contains("secondrule")); + + EXPECT_EQ(cfg.base_rules.size(), 2); + EXPECT_TRUE(cfg.base_rules.contains("1")); + EXPECT_TRUE(cfg.base_rules.contains("secondrule")); + + { + const rule_spec &rule = cfg.base_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + } + + { + const rule_spec &rule = cfg.base_rules["secondrule"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->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.at("type"), "flow2"); + EXPECT_STR(rule.tags.at("category"), "category2"); + EXPECT_STR(rule.tags.at("confidence"), "none"); + } +} + +TEST(TestBaseRuleParser, ParseMultipleRulesOneDuplicate) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("duplicate rule"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rules); + EXPECT_EQ(change.base_rules.size(), 1); + EXPECT_TRUE(change.base_rules.contains("1")); + + EXPECT_EQ(cfg.base_rules.size(), 1); + EXPECT_TRUE(cfg.base_rules.contains("1")); + + { + const rule_spec &rule = cfg.base_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + } +} + +TEST(TestBaseRuleParser, KeyPathTooLong) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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, y, z]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("key_path beyond maximum container depth"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, NegatedMatcherTooManyParameters) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: "!match_regex", parameters: {inputs: [{address: arg1}, {address: arg2}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("multiple targets for non-variadic argument"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, SupportedVersionedOperator) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v2"}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("rsp-930-003")); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rules); + EXPECT_EQ(change.base_rules.size(), 1); + EXPECT_EQ(cfg.base_rules.size(), 1); +} + +TEST(TestBaseRuleParser, UnsupportedVersionedOperator) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v20"}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("rsp-930-003")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, IncompatibleMinVersion) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 99.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("1")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, IncompatibleMaxVersion) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, max_version: 0.0.99, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("1")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestBaseRuleParser, CompatibleVersion) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 0.0.99, max_version: 2.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_base_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("1")); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rules); + EXPECT_EQ(change.base_rules.size(), 1); + EXPECT_EQ(cfg.base_rules.size(), 1); +} + +} // namespace diff --git a/tests/unit/configuration/configuration_manager_test.cpp b/tests/unit/configuration/configuration_manager_test.cpp new file mode 100644 index 000000000..1676eba66 --- /dev/null +++ b/tests/unit/configuration/configuration_manager_test.cpp @@ -0,0 +1,19 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/actions_parser.hpp" +#include "configuration/common/common.hpp" +#include "configuration/configuration_manager.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestConfigurationManager, EmptyConfig) {} + +} // namespace diff --git a/tests/unit/configuration/exclusion_data_parser_test.cpp b/tests/unit/configuration/exclusion_data_parser_test.cpp new file mode 100644 index 000000000..e5447a3f6 --- /dev/null +++ b/tests/unit/configuration/exclusion_data_parser_test.cpp @@ -0,0 +1,512 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/data_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestExclusionDataParser, ParseIPData) +{ + auto object = yaml_to_object( + R"([{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("ip_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::exclusion_data); + EXPECT_EQ(change.exclusion_data.size(), 1); + + EXPECT_EQ(cfg.exclusion_data.size(), 1); + EXPECT_EQ(cfg.exclusion_data["ip_data"].type, data_type::ip_with_expiration); +} + +TEST(TestExclusionDataParser, ParseStringData) +{ + auto object = yaml_to_object( + R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("usr_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::exclusion_data); + EXPECT_EQ(change.exclusion_data.size(), 1); + + EXPECT_EQ(cfg.exclusion_data.size(), 1); + EXPECT_EQ(cfg.exclusion_data["usr_data"].type, data_type::data_with_expiration); +} + +TEST(TestExclusionDataParser, ParseMultipleData) +{ + auto object = yaml_to_object( + 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)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("ip_data"), loaded.end()); + EXPECT_NE(loaded.find("usr_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::exclusion_data); + EXPECT_EQ(change.exclusion_data.size(), 2); + + EXPECT_EQ(cfg.exclusion_data.size(), 2); + EXPECT_EQ(cfg.exclusion_data["usr_data"].type, data_type::data_with_expiration); + EXPECT_EQ(cfg.exclusion_data["ip_data"].type, data_type::ip_with_expiration); +} + +TEST(TestExclusionDataParser, ParseUnknownDataID) +{ + auto object = yaml_to_object( + 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)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("ip_data"), loaded.end()); + EXPECT_NE(loaded.find("usr_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::exclusion_data); + EXPECT_EQ(change.exclusion_data.size(), 2); + + EXPECT_EQ(cfg.exclusion_data.size(), 2); + EXPECT_EQ(cfg.exclusion_data["ip_data"].type, data_type::ip_with_expiration); + EXPECT_EQ(cfg.exclusion_data["usr_data"].type, data_type::data_with_expiration); +} + +TEST(TestExclusionDataParser, ParseUnsupportedTypes) +{ + auto object = yaml_to_object( + R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: whatever, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 2); + EXPECT_NE(failed.find("ip_data"), failed.end()); + EXPECT_NE(failed.find("usr_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 2); + { + auto it = errors.find("unknown type 'blob_with_expiration'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); + } + + { + auto it = errors.find("unknown type 'whatever'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); + } + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestExclusionDataParser, ParseUnknownDataIDWithUnsupportedType) +{ + auto object = yaml_to_object( + R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("usr_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("unknown type 'blob_with_expiration'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestExclusionDataParser, ParseMissingType) +{ + auto object = + yaml_to_object(R"([{id: ip_data, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ip_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestExclusionDataParser, ParseMissingID) +{ + auto object = yaml_to_object( + R"([{type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestExclusionDataParser, ParseMissingData) +{ + auto object = yaml_to_object(R"([{id: ip_data, type: ip_with_expiration}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_exclusion_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ip_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'data'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +} // namespace diff --git a/tests/unit/configuration/input_filter_parser_test.cpp b/tests/unit/configuration/input_filter_parser_test.cpp new file mode 100644 index 000000000..4f1239a4f --- /dev/null +++ b/tests/unit/configuration/input_filter_parser_test.cpp @@ -0,0 +1,837 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/exclusion_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestInputFilterParser, ParseEmpty) +{ + object_limits limits; + auto object = yaml_to_object(R"([{id: 1, inputs: []}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("empty exclusion filter"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestInputFilterParser, ParseFilterWithoutID) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{inputs: [{address: http.client_ip}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestInputFilterParser, ParseDuplicateFilters) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}]}, {id: 1, inputs: [{address: usr.id}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("duplicate filter"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + EXPECT_TRUE(cfg.input_filters.contains("1")); +} + +TEST(TestInputFilterParser, ParseUnconditionalNoTargets) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 0); + EXPECT_TRUE(filter_it->second.filter); +} + +TEST(TestInputFilterParser, ParseUnconditionalTargetID) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + EXPECT_TRUE(cfg.input_filters.contains("1")); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestInputFilterParser, ParseUnconditionalTargetTags) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_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(TestInputFilterParser, ParseUnconditionalTargetPriority) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestInputFilterParser, ParseUnconditionalMultipleTargets) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}, {tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 2); + EXPECT_TRUE(filter_it->second.filter); + + { + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); + } + + { + const auto &target = filter_it->second.targets[1]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_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(TestInputFilterParser, ParseMultipleUnconditional) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}]}, {id: 2, inputs: [{address: usr.id}], rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("1"), loaded.end()); + EXPECT_NE(loaded.find("2"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 2); + EXPECT_TRUE(change.input_filters.contains("1")); + EXPECT_TRUE(change.input_filters.contains("2")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 2); + + { + const auto &filter_it = cfg.input_filters.find("1"); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); + } + + { + const auto &filter_it = cfg.input_filters.find("2"); + EXPECT_STR(filter_it->first, "2"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_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(TestInputFilterParser, ParseConditionalSingleCondition) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 1); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestInputFilterParser, ParseConditionalMultipleConditions) +{ + object_limits limits; + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], 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: .*}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); + + const auto &filter_it = cfg.input_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 3); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_TRUE(filter_it->second.filter); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestInputFilterParser, IncompatibleMinVersion) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}], min_version: 99.0.0}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestInputFilterParser, IncompatibleMaxVersion) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}], max_version: 0.0.99}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestInputFilterParser, CompatibleVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, inputs: [{address: http.client_ip}], min_version: 0.0.99, max_version: 2.0.0}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 0); + EXPECT_EQ(change.input_filters.size(), 1); + EXPECT_TRUE(change.input_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 0); + EXPECT_EQ(cfg.input_filters.size(), 1); +} + +} // namespace diff --git a/tests/unit/configuration/processor_parser_test.cpp b/tests/unit/configuration/processor_parser_test.cpp new file mode 100644 index 000000000..c142cb9fd --- /dev/null +++ b/tests/unit/configuration/processor_parser_test.cpp @@ -0,0 +1,952 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/processor_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestProcessorParser, ParseNoGenerator) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'generator'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseNoID) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseNoParameters) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, generator: extract_schema}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'parameters'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseNoMappings) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, generator: extract_schema, parameters: {}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'mappings'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseEmptyMappings) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, generator: extract_schema, parameters: {mappings: []}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("empty mappings"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseNoInput) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, generator: extract_schema, parameters: {mappings: [{}]}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'inputs'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseEmptyInput) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [], output: out}]}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("empty processor input mapping"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseNoOutput) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}]}]}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'output'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseUnknownGenerator) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: unknown, parameters: {mappings: [{inputs: [{address: in}], output: out}]}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("unknown generator 'unknown'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParseUseless) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: false, output: false}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("processor not used for evaluation or output"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + EXPECT_EQ(cfg.processors.size(), 0); +} + +TEST(TestProcessorParser, ParsePreprocessor) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: true, output: false}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(change.content, change_set::processors); + EXPECT_EQ(change.processors.size(), 1); + EXPECT_TRUE(change.processors.contains("1")); + EXPECT_EQ(cfg.processors.size(), 1); + EXPECT_TRUE(cfg.processors.contains("1")); +} + +TEST(TestProcessorParser, ParsePreprocessorWithOutput) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: true, output: true}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(change.content, change_set::processors); + EXPECT_EQ(change.processors.size(), 1); + EXPECT_TRUE(change.processors.contains("1")); + EXPECT_EQ(cfg.processors.size(), 1); + EXPECT_TRUE(cfg.processors.contains("1")); +} + +TEST(TestProcessorParser, ParsePostprocessor) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: false, output: true}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(change.content, change_set::processors); + EXPECT_EQ(change.processors.size(), 1); + EXPECT_TRUE(change.processors.contains("1")); + EXPECT_EQ(cfg.processors.size(), 1); + EXPECT_TRUE(cfg.processors.contains("1")); +} + +TEST(TestProcessorParser, ParseDuplicate) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: false, output: true},{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: true, output: false}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(change.content, change_set::processors); + EXPECT_EQ(change.processors.size(), 1); + EXPECT_TRUE(change.processors.contains("1")); + EXPECT_EQ(cfg.processors.size(), 1); + EXPECT_TRUE(cfg.processors.contains("1")); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("duplicate processor"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } +} + +TEST(TestProcessorParser, IncompatibleMinVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, min_version: 99.0.0, evaluate: false, output: true}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } +} + +TEST(TestProcessorParser, IncompatibleMaxVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, max_version: 0.0.99, evaluate: false, output: true}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } +} + +TEST(TestProcessorParser, CompatibleVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, min_version: 0.0.99, max_version: 2.0.0, evaluate: false, output: true}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto array = static_cast(parameter(object)); + parse_processors(array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_EQ(change.content, change_set::processors); + EXPECT_EQ(change.processors.size(), 1); + EXPECT_TRUE(change.processors.contains("1")); + EXPECT_EQ(cfg.processors.size(), 1); + EXPECT_TRUE(cfg.processors.contains("1")); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } +} + +} // namespace diff --git a/tests/unit/configuration/rule_data_parser_test.cpp b/tests/unit/configuration/rule_data_parser_test.cpp new file mode 100644 index 000000000..52236363f --- /dev/null +++ b/tests/unit/configuration/rule_data_parser_test.cpp @@ -0,0 +1,512 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/data_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestRuleDataParser, ParseIPData) +{ + auto object = yaml_to_object( + R"([{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("ip_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rule_data); + EXPECT_EQ(change.rule_data.size(), 1); + + EXPECT_EQ(cfg.rule_data.size(), 1); + EXPECT_EQ(cfg.rule_data["ip_data"].type, data_type::ip_with_expiration); +} + +TEST(TestRuleDataParser, ParseStringData) +{ + auto object = yaml_to_object( + R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("usr_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rule_data); + EXPECT_EQ(change.rule_data.size(), 1); + + EXPECT_EQ(cfg.rule_data.size(), 1); + EXPECT_EQ(cfg.rule_data["usr_data"].type, data_type::data_with_expiration); +} + +TEST(TestRuleDataParser, ParseMultipleData) +{ + auto object = yaml_to_object( + 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)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("ip_data"), loaded.end()); + EXPECT_NE(loaded.find("usr_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rule_data); + EXPECT_EQ(change.rule_data.size(), 2); + + EXPECT_EQ(cfg.rule_data.size(), 2); + EXPECT_EQ(cfg.rule_data["usr_data"].type, data_type::data_with_expiration); + EXPECT_EQ(cfg.rule_data["ip_data"].type, data_type::ip_with_expiration); +} + +TEST(TestRuleDataParser, ParseUnknownDataID) +{ + auto object = yaml_to_object( + 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)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("ip_data"), loaded.end()); + EXPECT_NE(loaded.find("usr_data"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::rule_data); + EXPECT_EQ(change.rule_data.size(), 2); + + EXPECT_EQ(cfg.rule_data.size(), 2); + EXPECT_EQ(cfg.rule_data["ip_data"].type, data_type::ip_with_expiration); + EXPECT_EQ(cfg.rule_data["usr_data"].type, data_type::data_with_expiration); +} + +TEST(TestRuleDataParser, ParseUnsupportedTypes) +{ + auto object = yaml_to_object( + R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: whatever, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 2); + EXPECT_NE(failed.find("ip_data"), failed.end()); + EXPECT_NE(failed.find("usr_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 2); + { + auto it = errors.find("unknown type 'blob_with_expiration'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); + } + + { + auto it = errors.find("unknown type 'whatever'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); + } + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleDataParser, ParseUnknownDataIDWithUnsupportedType) +{ + auto object = yaml_to_object( + R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("usr_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("unknown type 'blob_with_expiration'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleDataParser, ParseMissingType) +{ + auto object = + yaml_to_object(R"([{id: ip_data, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ip_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleDataParser, ParseMissingID) +{ + auto object = yaml_to_object( + R"([{type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleDataParser, ParseMissingData) +{ + auto object = yaml_to_object(R"([{id: ip_data, type: ip_with_expiration}])"); + auto input = static_cast(parameter(object)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_rule_data(input, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ip_data"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'data'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +} // namespace diff --git a/tests/unit/configuration/rule_filter_parser_test.cpp b/tests/unit/configuration/rule_filter_parser_test.cpp new file mode 100644 index 000000000..ef0ff6bcf --- /dev/null +++ b/tests/unit/configuration/rule_filter_parser_test.cpp @@ -0,0 +1,1066 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/exclusion_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestRuleFilterParser, ParseEmptyFilter) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("empty exclusion filter"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleFilterParser, ParseFilterWithoutID) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{rules_target: [{rule_id: 2939}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleFilterParser, ParseDuplicateUnconditional) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("duplicate filter"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + EXPECT_TRUE(cfg.rule_filters.contains("1")); +} + +TEST(TestRuleFilterParser, ParseUnconditionalTargetID) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestRuleFilterParser, ParseUnconditionalTargetTags) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_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(TestRuleFilterParser, ParseUnconditionalTargetPriority) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestRuleFilterParser, ParseUnconditionalMultipleTargets) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939},{tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 2); + + { + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); + } + + { + const auto &target = filter_it->second.targets[1]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_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(TestRuleFilterParser, ParseMultipleUnconditional) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 2, rules_target: [{tags: {type: rule, category: unknown}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("1"), loaded.end()); + EXPECT_NE(loaded.find("2"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 2); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_TRUE(change.rule_filters.contains("2")); + + EXPECT_EQ(cfg.rule_filters.size(), 2); + EXPECT_EQ(cfg.input_filters.size(), 0); + + { + const auto &filter_it = cfg.rule_filters.find("1"); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); + } + + { + const auto &filter_it = cfg.rule_filters.find("2"); + EXPECT_STR(filter_it->first, "2"); + + EXPECT_EQ(filter_it->second.expr->size(), 0); + EXPECT_EQ(filter_it->second.targets.size(), 1); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_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(TestRuleFilterParser, ParseDuplicateConditional) +{ + object_limits limits; + + auto object = yaml_to_object( + 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: .*}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); +} + +TEST(TestRuleFilterParser, ParseConditionalSingleCondition) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 1); + EXPECT_EQ(filter_it->second.targets.size(), 1); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestRuleFilterParser, ParseConditionalGlobal) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 1); + EXPECT_EQ(filter_it->second.targets.size(), 0); + EXPECT_EQ(filter_it->second.on_match, exclusion::filter_mode::bypass); +} + +TEST(TestRuleFilterParser, ParseConditionalMultipleConditions) +{ + object_limits limits; + + auto object = yaml_to_object( + 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: .*}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.expr->size(), 3); + EXPECT_EQ(filter_it->second.targets.size(), 1); + EXPECT_EQ(filter_it->second.on_match, exclusion::filter_mode::bypass); + + const auto &target = filter_it->second.targets[0]; + EXPECT_EQ(target.type, reference_type::id); + EXPECT_STR(target.ref_id, "2939"); + EXPECT_EQ(target.tags.size(), 0); +} + +TEST(TestRuleFilterParser, ParseOnMatchMonitor) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: monitor}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.on_match, exclusion::filter_mode::monitor); +} + +TEST(TestRuleFilterParser, ParseOnMatchBypass) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: bypass}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.on_match, exclusion::filter_mode::bypass); +} + +TEST(TestRuleFilterParser, ParseCustomOnMatch) +{ + object_limits limits; + + auto object = + yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: obliterate}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); + + const auto &filter_it = cfg.rule_filters.begin(); + EXPECT_STR(filter_it->first, "1"); + + EXPECT_EQ(filter_it->second.on_match, exclusion::filter_mode::custom); + EXPECT_STR(filter_it->second.custom_action, "obliterate"); +} + +TEST(TestRuleFilterParser, ParseInvalidOnMatch) +{ + object_limits limits; + + auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: ""}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("empty on_match value"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleFilterParser, IncompatibleMinVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], min_version: 99.0.0, on_match: monitor}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleFilterParser, IncompatibleMaxVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], max_version: 0.0.99, on_match: monitor}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_NE(skipped.find("1"), skipped.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleFilterParser, CompatibleVersion) +{ + object_limits limits; + + auto object = yaml_to_object( + R"([{id: 1, rules_target: [{rule_id: 2939}], min_version: 0.0.99, max_version: 2.0.0, on_match: monitor}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto filters_array = static_cast(parameter(object)); + parse_filters(filters_array, collector, section, limits); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::filters); + EXPECT_EQ(change.rule_filters.size(), 1); + EXPECT_EQ(change.input_filters.size(), 0); + EXPECT_TRUE(change.rule_filters.contains("1")); + EXPECT_EQ(cfg.rule_filters.size(), 1); + EXPECT_EQ(cfg.input_filters.size(), 0); +} + +} // namespace diff --git a/tests/unit/configuration/rule_override_parser_test.cpp b/tests/unit/configuration/rule_override_parser_test.cpp new file mode 100644 index 000000000..4c0141bc8 --- /dev/null +++ b/tests/unit/configuration/rule_override_parser_test.cpp @@ -0,0 +1,450 @@ +// 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 2025 Datadog, Inc. + +#include "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/rule_override_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestRuleOverrideParser, ParseRuleOverrideWithoutSideEffects) +{ + auto object = yaml_to_object(R"([{rules_target: [{tags: {confidence: 1}}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("rule override without side-effects"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleOverrideParser, ParseRuleOverrideWithoutTargets) +{ + auto object = yaml_to_object(R"([{rules_target: [{}], enabled: false}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("rule override with no targets"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleOverrideParser, ParseRuleOverride) +{ + auto object = + yaml_to_object(R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("index:0"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.overrides_by_id.size(), 0); + EXPECT_EQ(change.overrides_by_tags.size(), 1); + + EXPECT_EQ(cfg.overrides_by_id.size(), 0); + EXPECT_EQ(cfg.overrides_by_tags.size(), 1); + + auto &ovrd = cfg.overrides_by_tags.begin()->second; + 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, reference_type::tags); + EXPECT_TRUE(target.ref_id.empty()); + EXPECT_EQ(target.tags.size(), 1); + EXPECT_STR(target.tags["confidence"], "1"); +} + +TEST(TestRuleOverrideParser, ParseMultipleRuleOverrides) +{ + auto object = yaml_to_object( + R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block]},{rules_target: [{rule_id: 1}], enabled: false}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("index:0"), loaded.end()); + EXPECT_NE(loaded.find("index:1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.overrides_by_id.size(), 1); + EXPECT_EQ(change.overrides_by_tags.size(), 1); + + EXPECT_EQ(cfg.overrides_by_id.size(), 1); + EXPECT_EQ(cfg.overrides_by_tags.size(), 1); + + { + auto &ovrd = cfg.overrides_by_tags.begin()->second; + 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, reference_type::tags); + EXPECT_TRUE(target.ref_id.empty()); + EXPECT_EQ(target.tags.size(), 1); + EXPECT_STR(target.tags["confidence"], "1"); + } + + { + auto &ovrd = cfg.overrides_by_id.begin()->second; + 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, reference_type::id); + EXPECT_STR(target.ref_id, "1"); + EXPECT_EQ(target.tags.size(), 0); + } +} + +TEST(TestRuleOverrideParser, ParseInconsistentRuleOverride) +{ + auto object = yaml_to_object( + R"([{rules_target: [{tags: {confidence: 1}}, {rule_id: 1}], on_match: [block], enabled: false}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("rule override targets rules and tags"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestRuleOverrideParser, ParseRuleOverrideForTags) +{ + auto object = yaml_to_object( + R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block], tags: {category: new_category, threshold: 25}}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("index:0"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.overrides_by_id.size(), 0); + EXPECT_EQ(change.overrides_by_tags.size(), 1); + + EXPECT_EQ(cfg.overrides_by_id.size(), 0); + EXPECT_EQ(cfg.overrides_by_tags.size(), 1); + + auto &ovrd = cfg.overrides_by_tags.begin()->second; + 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); + EXPECT_EQ(ovrd.tags.size(), 2); + EXPECT_STR(ovrd.tags["category"], "new_category"); + EXPECT_STR(ovrd.tags["threshold"], "25"); + + auto &target = ovrd.targets[0]; + EXPECT_EQ(target.type, reference_type::tags); + EXPECT_TRUE(target.ref_id.empty()); + EXPECT_EQ(target.tags.size(), 1); + EXPECT_STR(target.tags["confidence"], "1"); +} + +TEST(TestRuleOverrideParser, ParseInvalidTagsField) +{ + auto object = yaml_to_object( + R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block], tags: [{category: new_category}, {threshold: 25}]}])"); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + auto override_array = static_cast(parameter(object)); + parse_overrides(override_array, collector, section); + ddwaf_object_free(&object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + + auto it = errors.find("bad cast, expected 'map', obtained 'array'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +} // namespace diff --git a/tests/unit/configuration/scanner_parser_test.cpp b/tests/unit/configuration/scanner_parser_test.cpp new file mode 100644 index 000000000..5a30af917 --- /dev/null +++ b/tests/unit/configuration/scanner_parser_test.cpp @@ -0,0 +1,973 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/scanner_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestScannerParser, ParseKeyOnlyScanner) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("ecd"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.content, change_set::scanners); + EXPECT_EQ(change.scanners.size(), 1); + EXPECT_TRUE(change.scanners.contains("ecd")); + ASSERT_EQ(cfg.scanners.size(), 1); + EXPECT_TRUE(cfg.scanners.contains("ecd")); + + const auto *const scnr = cfg.scanners.find_by_id("ecd"); + ASSERT_NE(scnr, nullptr); + EXPECT_STREQ(scnr->get_id().data(), "ecd"); + std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; + EXPECT_EQ(scnr->get_tags(), tags); + + ddwaf_object value; + ddwaf_object_string(&value, "dog@datadoghq.com"); + EXPECT_TRUE(scnr->eval("email", value)); + EXPECT_FALSE(scnr->eval("mail", value)); + ddwaf_object_free(&value); + + ddwaf_object_string(&value, "ansodinsod"); + EXPECT_TRUE(scnr->eval("email", value)); + ddwaf_object_free(&value); +} + +TEST(TestScannerParser, ParseValueOnlyScanner) +{ + auto definition = json_to_object( + R"([{"id":"ecd","value":{"operator":"match_regex","parameters":{"regex":"@"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("ecd"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.content, change_set::scanners); + EXPECT_EQ(change.scanners.size(), 1); + EXPECT_TRUE(change.scanners.contains("ecd")); + ASSERT_EQ(cfg.scanners.size(), 1); + EXPECT_TRUE(cfg.scanners.contains("ecd")); + + const auto *const scnr = cfg.scanners.find_by_id("ecd"); + ASSERT_NE(scnr, nullptr); + EXPECT_STREQ(scnr->get_id().data(), "ecd"); + std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; + EXPECT_EQ(scnr->get_tags(), tags); + + ddwaf_object value; + ddwaf_object_string(&value, "dog@datadoghq.com"); + EXPECT_TRUE(scnr->eval("email", value)); + EXPECT_TRUE(scnr->eval("mail", value)); + ddwaf_object_free(&value); + + ddwaf_object_string(&value, "ansodinsod"); + EXPECT_FALSE(scnr->eval("email", value)); + ddwaf_object_free(&value); +} + +TEST(TestScannerParser, ParseKeyValueScanner) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"@"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("ecd"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.content, change_set::scanners); + EXPECT_EQ(change.scanners.size(), 1); + EXPECT_TRUE(change.scanners.contains("ecd")); + ASSERT_EQ(cfg.scanners.size(), 1); + EXPECT_TRUE(cfg.scanners.contains("ecd")); + + const auto *const scnr = cfg.scanners.find_by_id("ecd"); + ASSERT_NE(scnr, nullptr); + EXPECT_STREQ(scnr->get_id().data(), "ecd"); + std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; + EXPECT_EQ(scnr->get_tags(), tags); + + ddwaf_object value; + ddwaf_object_string(&value, "dog@datadoghq.com"); + EXPECT_TRUE(scnr->eval("email", value)); + EXPECT_FALSE(scnr->eval("mail", value)); + ddwaf_object_free(&value); + + ddwaf_object_string(&value, "ansodinsod"); + EXPECT_FALSE(scnr->eval("email", value)); + ddwaf_object_free(&value); +} + +TEST(TestScannerParser, ParseNoID) +{ + auto definition = json_to_object( + R"([{"key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"@"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseNoTags) +{ + auto definition = json_to_object( + R"([{"id":"error","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"@"}}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("error"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'tags'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("error"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseNoKeyValue) +{ + auto definition = + json_to_object(R"([{"id":"error","tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("error"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("scanner has no key or value matcher"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("error"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseDuplicate) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}},{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("ecd"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("duplicate scanner"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.content, change_set::scanners); + EXPECT_EQ(change.scanners.size(), 1); + EXPECT_TRUE(change.scanners.contains("ecd")); + ASSERT_EQ(cfg.scanners.size(), 1); + EXPECT_TRUE(cfg.scanners.contains("ecd")); +} + +TEST(TestScannerParser, ParseKeyNoOperator) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'operator'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseKeyNoParameters) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex"},"value":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'parameters'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseValueNoOperator) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'operator'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseValueNoParameters) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex"},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'parameters'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseUnknownMatcher) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"what","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("unknown matcher: what"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, ParseRuleDataID) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"exact_match","parameters":{"data":"invalid"}},"tags":{"type":"email","category":"pii"}}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("ecd"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("dynamic data on scanner condition"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("ecd"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, IncompatibleMinVersion) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "min_version": "99.0.0"}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("ecd")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, IncompatibleMaxVersion) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "max_version": "0.0.99"}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("ecd")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestScannerParser, CompatibleVersion) +{ + auto definition = json_to_object( + R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "min_version": "0.0.99", "max_version": "2.0.0"}])"); + auto scanners_array = static_cast(parameter(definition)); + + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + parse_scanners(scanners_array, collector, section); + ddwaf_object_free(&definition); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("ecd")); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(change.content, change_set::scanners); + EXPECT_EQ(change.scanners.size(), 1); + EXPECT_TRUE(change.scanners.contains("ecd")); + ASSERT_EQ(cfg.scanners.size(), 1); + EXPECT_TRUE(cfg.scanners.contains("ecd")); +} + +} // namespace diff --git a/tests/unit/parser_transformers_test.cpp b/tests/unit/configuration/transformer_parser_test.cpp similarity index 94% rename from tests/unit/parser_transformers_test.cpp rename to tests/unit/configuration/transformer_parser_test.cpp index 4b27e509d..3aaee59fa 100644 --- a/tests/unit/parser_transformers_test.cpp +++ b/tests/unit/configuration/transformer_parser_test.cpp @@ -4,12 +4,12 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include "parser/common.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/transformer_parser.hpp" #include "common/gtest_utils.hpp" using namespace ddwaf; -using namespace ddwaf::parser; // NOLINTBEGIN(cppcoreguidelines-macro-usage,bugprone-unchecked-optional-access) #define EXPECT_OPTEQ(opt, expected) \ @@ -19,7 +19,7 @@ using namespace ddwaf::parser; namespace { -TEST(TestParserTransformers, ValidTransformers) +TEST(TestTransformerParser, ValidTransformers) { EXPECT_OPTEQ(transformer_from_string("lowercase"), transformer_id::lowercase); EXPECT_OPTEQ(transformer_from_string("remove_nulls"), transformer_id::remove_nulls); @@ -42,7 +42,7 @@ TEST(TestParserTransformers, ValidTransformers) EXPECT_OPTEQ(transformer_from_string("unicode_normalize"), transformer_id::unicode_normalize); } -TEST(TestParserTransformers, AlisedTransformers) +TEST(TestTransformerParser, AlisedTransformers) { EXPECT_OPTEQ(transformer_from_string("removeNulls"), transformer_id::remove_nulls); EXPECT_OPTEQ( @@ -63,7 +63,7 @@ TEST(TestParserTransformers, AlisedTransformers) EXPECT_OPTEQ(transformer_from_string("removeComments"), transformer_id::remove_comments); } -TEST(TestParserTransformers, InvalidTransformers) +TEST(TestTransformerParser, InvalidTransformers) { EXPECT_FALSE(transformer_from_string("LoWeRcAse")); EXPECT_FALSE(transformer_from_string("raAndom")); diff --git a/tests/unit/configuration/user_rule_parser_test.cpp b/tests/unit/configuration/user_rule_parser_test.cpp new file mode 100644 index 000000000..0d85a7a71 --- /dev/null +++ b/tests/unit/configuration/user_rule_parser_test.cpp @@ -0,0 +1,923 @@ +// 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 "common/gtest_utils.hpp" +#include "configuration/common/common.hpp" +#include "configuration/common/configuration.hpp" +#include "configuration/rule_parser.hpp" +#include "parameter.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestUserRuleParser, ParseRule) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::custom_rules); + EXPECT_EQ(change.user_rules.size(), 1); + EXPECT_TRUE(change.user_rules.contains("1")); + + EXPECT_EQ(cfg.user_rules.size(), 1); + EXPECT_TRUE(cfg.user_rules.contains("1")); + + const rule_spec &rule = cfg.user_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); +} + +TEST(TestUserRuleParser, ParseRuleWithoutType) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'type'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, ParseRuleInvalidTransformer) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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], transformers: [unknown]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("invalid transformer unknown"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, ParseRuleWithoutID) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("index:0"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'id'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("index:0"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, ParseMultipleRules) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("1"), loaded.end()); + EXPECT_NE(loaded.find("secondrule"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::custom_rules); + EXPECT_EQ(change.user_rules.size(), 2); + EXPECT_TRUE(change.user_rules.contains("1")); + EXPECT_TRUE(change.user_rules.contains("secondrule")); + + EXPECT_EQ(cfg.user_rules.size(), 2); + EXPECT_TRUE(cfg.user_rules.contains("1")); + EXPECT_TRUE(cfg.user_rules.contains("secondrule")); + + { + const rule_spec &rule = cfg.user_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + } + + { + const rule_spec &rule = cfg.user_rules["secondrule"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->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.at("type"), "flow2"); + EXPECT_STR(rule.tags.at("category"), "category2"); + EXPECT_STR(rule.tags.at("confidence"), "none"); + } +} + +TEST(TestUserRuleParser, ParseMultipleRulesOneInvalid) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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)); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 2); + EXPECT_NE(loaded.find("1"), loaded.end()); + EXPECT_NE(loaded.find("secondrule"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("error"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("missing key 'conditions'"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("error"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::custom_rules); + EXPECT_EQ(change.user_rules.size(), 2); + EXPECT_TRUE(change.user_rules.contains("1")); + EXPECT_TRUE(change.user_rules.contains("secondrule")); + + EXPECT_EQ(cfg.user_rules.size(), 2); + EXPECT_TRUE(cfg.user_rules.contains("1")); + EXPECT_TRUE(cfg.user_rules.contains("secondrule")); + + { + const rule_spec &rule = cfg.user_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + } + + { + const rule_spec &rule = cfg.user_rules["secondrule"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->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.at("type"), "flow2"); + EXPECT_STR(rule.tags.at("category"), "category2"); + EXPECT_STR(rule.tags.at("confidence"), "none"); + } +} + +TEST(TestUserRuleParser, ParseMultipleRulesOneDuplicate) +{ + object_limits limits; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_NE(loaded.find("1"), loaded.end()); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("duplicate rule"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::custom_rules); + EXPECT_EQ(change.user_rules.size(), 1); + EXPECT_TRUE(change.user_rules.contains("1")); + + EXPECT_EQ(cfg.user_rules.size(), 1); + EXPECT_TRUE(cfg.user_rules.contains("1")); + + { + const rule_spec &rule = cfg.user_rules["1"]; + EXPECT_TRUE(rule.enabled); + EXPECT_EQ(rule.expr->size(), 3); + EXPECT_EQ(rule.actions.size(), 0); + EXPECT_STR(rule.name, "rule1"); + EXPECT_EQ(rule.tags.size(), 2); + EXPECT_STR(rule.tags.at("type"), "flow1"); + EXPECT_STR(rule.tags.at("category"), "category1"); + } +} + +TEST(TestUserRuleParser, KeyPathTooLong) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + 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, y, z]}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("key_path beyond maximum container depth"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, NegatedMatcherTooManyParameters) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: "!match_regex", parameters: {inputs: [{address: arg1}, {address: arg2}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("multiple targets for non-variadic argument"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, SupportedVersionedOperator) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v2"}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("rsp-930-003")); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::custom_rules); + EXPECT_EQ(change.user_rules.size(), 1); + EXPECT_EQ(cfg.user_rules.size(), 1); +} + +TEST(TestUserRuleParser, UnsupportedVersionedOperator) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v20"}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("rsp-930-003")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, IncompatibleMinVersion) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 99.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("1")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, IncompatibleMaxVersion) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, max_version: 0.0.99, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 1); + EXPECT_TRUE(skipped.contains("1")); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_TRUE(change.empty()); + EXPECT_TRUE(change.actions.empty()); + EXPECT_TRUE(change.base_rules.empty()); + EXPECT_TRUE(change.user_rules.empty()); + EXPECT_TRUE(change.exclusion_data.empty()); + EXPECT_TRUE(change.rule_data.empty()); + EXPECT_TRUE(change.rule_filters.empty()); + EXPECT_TRUE(change.input_filters.empty()); + EXPECT_TRUE(change.processors.empty()); + EXPECT_TRUE(change.scanners.empty()); + EXPECT_TRUE(change.overrides_by_id.empty()); + EXPECT_TRUE(change.overrides_by_tags.empty()); + + EXPECT_TRUE(cfg.actions.empty()); + EXPECT_TRUE(cfg.base_rules.empty()); + EXPECT_TRUE(cfg.user_rules.empty()); + EXPECT_TRUE(cfg.exclusion_data.empty()); + EXPECT_TRUE(cfg.rule_data.empty()); + EXPECT_TRUE(cfg.rule_filters.empty()); + EXPECT_TRUE(cfg.input_filters.empty()); + EXPECT_TRUE(cfg.processors.empty()); + EXPECT_TRUE(cfg.scanners.empty()); + EXPECT_TRUE(cfg.overrides_by_id.empty()); + EXPECT_TRUE(cfg.overrides_by_tags.empty()); +} + +TEST(TestUserRuleParser, CompatibleVersion) +{ + object_limits limits; + limits.max_container_depth = 2; + configuration_spec cfg; + configuration_change_spec change; + configuration_collector collector{change, cfg}; + ruleset_info::section_info section; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 0.0.99, max_version: 2.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + parse_user_rules(rule_array, collector, section, limits); + + ddwaf_object_free(&rule_object); + + { + parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 1); + EXPECT_TRUE(loaded.contains("1")); + + auto failed = at(root_map, "failed"); + EXPECT_EQ(failed.size(), 0); + + auto skipped = at(root_map, "skipped"); + EXPECT_EQ(skipped.size(), 0); + + auto errors = at(root_map, "errors"); + EXPECT_EQ(errors.size(), 0); + + ddwaf_object_free(&root); + } + + EXPECT_FALSE(change.empty()); + EXPECT_EQ(change.content, change_set::custom_rules); + + EXPECT_EQ(change.user_rules.size(), 1); + EXPECT_EQ(cfg.user_rules.size(), 1); +} + +} // namespace diff --git a/tests/unit/context_test.cpp b/tests/unit/context_test.cpp index 47f2537be..d3ed15cbc 100644 --- a/tests/unit/context_test.cpp +++ b/tests/unit/context_test.cpp @@ -380,9 +380,9 @@ TEST(TestContext, MatchMultipleRulesWithPrioritySingleRun) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - rules.emplace_back(std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"block"})); - rules.back()->set_verdict(core_rule::verdict_type::block); + rules.emplace_back(std::make_shared("id2", "name2", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); } ruleset->insert_rules(rules, {}); @@ -531,9 +531,9 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) std::unordered_map tags{ {"type", "type"}, {"category", "category2"}}; - rules.emplace_back(std::make_shared( - "id2", "name2", std::move(tags), builder.build(), std::vector{"block"})); - rules.back()->set_verdict(core_rule::verdict_type::block); + rules.emplace_back(std::make_shared("id2", "name2", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); } ruleset->insert_rules(rules, {}); @@ -614,9 +614,9 @@ TEST(TestContext, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) std::unordered_map tags{ {"type", "type"}, {"category", "category1"}}; - rules.emplace_back(std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"})); - rules.back()->set_verdict(core_rule::verdict_type::block); + rules.emplace_back(std::make_shared("id1", "name1", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); } { @@ -740,9 +740,9 @@ TEST(TestContext, MatchPriorityCollectionsSingleRun) std::unordered_map tags{ {"type", "type1"}, {"category", "category1"}}; - rules.emplace_back(std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"})); - rules.back()->set_verdict(core_rule::verdict_type::block); + rules.emplace_back(std::make_shared("id1", "name1", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); } { @@ -847,9 +847,9 @@ TEST(TestContext, MatchMultiplePriorityCollectionsDoubleRun) std::unordered_map tags{ {"type", "type1"}, {"category", "category1"}}; - rules.emplace_back(std::make_shared( - "id1", "name1", std::move(tags), builder.build(), std::vector{"block"})); - rules.back()->set_verdict(core_rule::verdict_type::block); + rules.emplace_back(std::make_shared("id1", "name1", std::move(tags), + builder.build(), std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block)); } { @@ -1120,9 +1120,9 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralBypassPersistentMonitor) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); - rule->set_actions({"block"}); - rule->set_verdict(core_rule::verdict_type::block); + rule = std::make_shared("id", "name", std::move(tags), builder.build(), + std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block); ruleset->insert_rules({rule}, {}); } @@ -1198,9 +1198,9 @@ TEST(TestContext, OverlappingRuleFiltersEphemeralMonitorPersistentBypass) std::unordered_map tags{ {"type", "type"}, {"category", "category"}}; - rule = std::make_shared("id", "name", std::move(tags), builder.build()); - rule->set_actions({"block"}); - rule->set_verdict(core_rule::verdict_type::block); + rule = std::make_shared("id", "name", std::move(tags), builder.build(), + std::vector{"block"}, true, core_rule::source_type::base, + core_rule::verdict_type::block); ruleset->insert_rules({rule}, {}); } diff --git a/tests/unit/event_serializer_test.cpp b/tests/unit/event_serializer_test.cpp index 5d644bdc5..1309d1f00 100644 --- a/tests/unit/event_serializer_test.cpp +++ b/tests/unit/event_serializer_test.cpp @@ -6,6 +6,7 @@ #include "common/gtest_utils.hpp" +#include "builder/action_mapper_builder.hpp" #include "event.hpp" #include "rule.hpp" #include "utils.hpp" diff --git a/tests/unit/indexed_multivector_test.cpp b/tests/unit/indexed_multivector_test.cpp new file mode 100644 index 000000000..711baf1d6 --- /dev/null +++ b/tests/unit/indexed_multivector_test.cpp @@ -0,0 +1,110 @@ +// 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 2025 Datadog, Inc. + +#include "common/gtest_utils.hpp" +#include "indexed_multivector.hpp" + +using namespace ddwaf; + +namespace { + +TEST(TestIndexedMultivector, SingleElement) +{ + indexed_multivector ivec; + ASSERT_TRUE(ivec.empty()); + + std::vector vec{"a", "b", "c", "d"}; + ivec.emplace("default", vec); + ASSERT_FALSE(ivec.empty()); + + auto vec_it = vec.begin(); + for (const auto &v : ivec) { + ASSERT_NE(vec_it, vec.end()); + EXPECT_EQ(*vec_it, v); + ++vec_it; + } + + ivec.erase("default"); + + ASSERT_EQ(ivec.begin(), ivec.end()); + ASSERT_TRUE(ivec.empty()); +} + +TEST(TestIndexedMultivector, MultipleElements) +{ + indexed_multivector ivec; + ASSERT_TRUE(ivec.empty()); + + ivec.emplace("vec1", {"a", "e", "g", "else"}); + ASSERT_FALSE(ivec.empty()); + + { + std::unordered_set all_items{"a", "e", "g", "else"}; + for (const auto &v : ivec) { + EXPECT_TRUE(all_items.contains(v)); + all_items.erase(v); + } + EXPECT_EQ(all_items.size(), 0); + } + + ivec.emplace("vec2", {"d", "c", "long", "something"}); + ASSERT_FALSE(ivec.empty()); + + { + std::unordered_set all_items{ + "a", "c", "d", "e", "g", "long", "something", "else"}; + for (const auto &v : ivec) { + EXPECT_TRUE(all_items.contains(v)); + all_items.erase(v); + } + EXPECT_EQ(all_items.size(), 0); + } + + ivec.emplace("vec3", {"b", "f", "string"}); + ASSERT_FALSE(ivec.empty()); + + { + std::unordered_set all_items{ + "a", "b", "c", "d", "e", "f", "g", "long", "string", "something", "else"}; + for (const auto &v : ivec) { + EXPECT_TRUE(all_items.contains(v)); + all_items.erase(v); + } + EXPECT_EQ(all_items.size(), 0); + } + + ivec.erase("vec1"); + ASSERT_FALSE(ivec.empty()); + + { + std::unordered_set all_items{ + "b", "c", "d", "f", "long", "string", "something"}; + for (const auto &v : ivec) { + EXPECT_TRUE(all_items.contains(v)); + all_items.erase(v); + } + EXPECT_EQ(all_items.size(), 0); + } + + ivec.erase("vec3"); + ASSERT_FALSE(ivec.empty()); + + { + std::unordered_set all_items{"c", "d", "long", "something"}; + for (const auto &v : ivec) { + EXPECT_TRUE(all_items.contains(v)); + all_items.erase(v); + } + EXPECT_EQ(all_items.size(), 0); + } + + ivec.erase("vec2"); + + ASSERT_EQ(ivec.begin(), ivec.end()); + ASSERT_TRUE(ivec.empty()); +} + +} // namespace diff --git a/tests/unit/indexer_test.cpp b/tests/unit/indexer_test.cpp new file mode 100644 index 000000000..c2eb5410b --- /dev/null +++ b/tests/unit/indexer_test.cpp @@ -0,0 +1,523 @@ +// 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 2025 Datadog, Inc. + +#include "common/gtest_utils.hpp" +#include "indexer.hpp" + +using namespace ddwaf; + +namespace { + +struct test_object { + test_object(std::string id_, std::unordered_map tags_) + : id(std::move(id_)), tags(std::move(tags_)) + {} + + std::string_view get_id() const { return id; } + const std::unordered_map &get_tags() const { return tags; } + + std::string id; + std::unordered_map tags; +}; + +TEST(TestIndexer, FindSingleElement) +{ + indexer index; + auto object = + std::make_shared("id", decltype(test_object::tags){{"tag", "value"}}); + index.emplace(object); + EXPECT_EQ(index.size(), 1); + + EXPECT_TRUE(index.contains("id")); + EXPECT_EQ(index.find_by_id("id"), object.get()); + + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(object.get())); +} + +TEST(TestIndexer, EraseSingleElementById) +{ + indexer index; + auto object = + std::make_shared("id", decltype(test_object::tags){{"tag", "value"}}); + index.emplace(object); + EXPECT_EQ(index.size(), 1); + + index.erase("id"); + EXPECT_EQ(index.size(), 0); + + EXPECT_FALSE(index.contains("id")); + EXPECT_EQ(index.find_by_id("id"), nullptr); + + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value"}}); + EXPECT_TRUE(items.empty()); +} + +TEST(TestIndexer, EraseSingleElementByIterator) +{ + indexer index; + auto object = + std::make_shared("id", decltype(test_object::tags){{"tag", "value"}}); + index.emplace(object); + EXPECT_EQ(index.size(), 1); + + auto it = index.begin(); + index.erase(it); + EXPECT_EQ(index.size(), 0); + + EXPECT_FALSE(index.contains("id")); + EXPECT_EQ(index.find_by_id("id"), nullptr); + + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value"}}); + EXPECT_TRUE(items.empty()); +} + +TEST(TestIndexer, ClearSingleElement) +{ + indexer index; + auto object = + std::make_shared("id", decltype(test_object::tags){{"tag", "value"}}); + index.emplace(object); + EXPECT_EQ(index.size(), 1); + + index.clear(); + EXPECT_EQ(index.size(), 0); + + EXPECT_FALSE(index.contains("id")); + EXPECT_EQ(index.find_by_id("id"), nullptr); + + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value"}}); + EXPECT_TRUE(items.empty()); +} + +TEST(TestIndexer, IterateSingleElement) +{ + indexer index; + auto object = + std::make_shared("id", decltype(test_object::tags){{"tag", "value"}}); + index.emplace(object); + EXPECT_EQ(index.size(), 1); + + for (const auto &value : index) { EXPECT_EQ(value, object); } +} + +TEST(TestIndexer, FindMultipleElements) +{ + indexer index; + std::unordered_map> objects{ + {"id0", std::make_shared( + "id0", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id1", std::make_shared( + "id1", decltype(test_object::tags){{"tag", "value2"}, {"common", "value"}})}, + {"id2", std::make_shared( + "id2", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id3", + std::make_shared("id3", decltype(test_object::tags){{"tag", "value2"}, + {"common", "value"}, {"random", "value"}})}, + }; + + for (const auto &[id, o] : objects) { index.emplace(o); } + + EXPECT_EQ(index.size(), 4); + + EXPECT_TRUE(index.contains("id0")); + EXPECT_TRUE(index.contains("id1")); + EXPECT_TRUE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id0"), objects["id0"].get()); + EXPECT_EQ(index.find_by_id("id1"), objects["id1"].get()); + EXPECT_EQ(index.find_by_id("id2"), objects["id2"].get()); + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value1"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id0"].get())); + EXPECT_TRUE(items.contains(objects["id2"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 4); + EXPECT_TRUE(items.contains(objects["id0"].get())); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id2"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"random", "value"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } +} + +TEST(TestIndexer, EraseMultipleElementsById) +{ + indexer index; + std::unordered_map> objects{ + {"id0", std::make_shared( + "id0", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id1", std::make_shared( + "id1", decltype(test_object::tags){{"tag", "value2"}, {"common", "value"}})}, + {"id2", std::make_shared( + "id2", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id3", + std::make_shared("id3", decltype(test_object::tags){{"tag", "value2"}, + {"common", "value"}, {"random", "value"}})}, + }; + + for (const auto &[id, o] : objects) { index.emplace(o); } + + EXPECT_EQ(index.size(), 4); + + index.erase("id0"); + EXPECT_EQ(index.size(), 3); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_TRUE(index.contains("id1")); + EXPECT_TRUE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id1"), objects["id1"].get()); + EXPECT_EQ(index.find_by_id("id2"), objects["id2"].get()); + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value1"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id2"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 3); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id2"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + index.erase("id2"); + EXPECT_EQ(index.size(), 2); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_TRUE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id1"), objects["id1"].get()); + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value1"}}); + EXPECT_EQ(items.size(), 0); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + index.erase("id1"); + EXPECT_EQ(index.size(), 1); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_FALSE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + index.erase("id3"); + EXPECT_EQ(index.size(), 0); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_FALSE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_FALSE(index.contains("id3")); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 0); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 0); + } +} + +TEST(TestIndexer, EraseMultipleElementsByIterator) +{ + indexer index; + std::unordered_map> objects{ + {"id0", std::make_shared( + "id0", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id1", std::make_shared( + "id1", decltype(test_object::tags){{"tag", "value2"}, {"common", "value"}})}, + {"id2", std::make_shared( + "id2", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id3", + std::make_shared("id3", decltype(test_object::tags){{"tag", "value2"}, + {"common", "value"}, {"random", "value"}})}, + }; + + for (const auto &[id, o] : objects) { index.emplace(o); } + + EXPECT_EQ(index.size(), 4); + + auto find_iterator = [&index](std::string_view id) { + for (auto it = index.begin(); it != index.end(); ++it) { + if ((*it)->get_id() == id) { + return it; + } + } + return index.end(); + }; + + { + auto it = find_iterator("id0"); + index.erase(it); + } + EXPECT_EQ(index.size(), 3); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_TRUE(index.contains("id1")); + EXPECT_TRUE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id1"), objects["id1"].get()); + EXPECT_EQ(index.find_by_id("id2"), objects["id2"].get()); + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value1"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id2"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 3); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id2"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto it = find_iterator("id2"); + index.erase(it); + } + EXPECT_EQ(index.size(), 2); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_TRUE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id1"), objects["id1"].get()); + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value1"}}); + EXPECT_EQ(items.size(), 0); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 2); + EXPECT_TRUE(items.contains(objects["id1"].get())); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto it = find_iterator("id1"); + index.erase(it); + } + EXPECT_EQ(index.size(), 1); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_FALSE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_TRUE(index.contains("id3")); + + EXPECT_EQ(index.find_by_id("id3"), objects["id3"].get()); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(objects["id3"].get())); + } + + { + auto it = find_iterator("id3"); + index.erase(it); + } + EXPECT_EQ(index.size(), 0); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_FALSE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_FALSE(index.contains("id3")); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 0); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 0); + } +} + +TEST(TestIndexer, ClearMultipleElements) +{ + indexer index; + std::unordered_map> objects{ + {"id0", std::make_shared( + "id0", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id1", std::make_shared( + "id1", decltype(test_object::tags){{"tag", "value2"}, {"common", "value"}})}, + {"id2", std::make_shared( + "id2", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id3", + std::make_shared("id3", decltype(test_object::tags){{"tag", "value2"}, + {"common", "value"}, {"random", "value"}})}, + }; + + for (const auto &[id, o] : objects) { index.emplace(o); } + + EXPECT_EQ(index.size(), 4); + + index.clear(); + EXPECT_EQ(index.size(), 0); + + EXPECT_FALSE(index.contains("id0")); + EXPECT_FALSE(index.contains("id1")); + EXPECT_FALSE(index.contains("id2")); + EXPECT_FALSE(index.contains("id3")); + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value1"}}); + EXPECT_EQ(items.size(), 0); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value2"}}); + EXPECT_EQ(items.size(), 0); + } + + { + auto items = index.find_by_tags(decltype(test_object::tags){{"common", "value"}}); + EXPECT_EQ(items.size(), 0); + } +} + +TEST(TestIndexer, IterateMultipleElements) +{ + indexer index; + std::unordered_map> objects{ + {"id0", std::make_shared( + "id0", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id1", std::make_shared( + "id1", decltype(test_object::tags){{"tag", "value2"}, {"common", "value"}})}, + {"id2", std::make_shared( + "id2", decltype(test_object::tags){{"tag", "value1"}, {"common", "value"}})}, + {"id3", + std::make_shared("id3", decltype(test_object::tags){{"tag", "value2"}, + {"common", "value"}, {"random", "value"}})}, + }; + + for (const auto &[id, o] : objects) { index.emplace(o); } + + EXPECT_EQ(index.size(), 4); + + for (const auto &item : index) { + auto it = objects.find(std::string{item->get_id()}); + EXPECT_NE(it, objects.end()); + objects.erase(it); + } + EXPECT_TRUE(objects.empty()); +} + +TEST(TestIndexer, EraseNonExistentKey) +{ + indexer index; + auto object = + std::make_shared("id", decltype(test_object::tags){{"tag", "value"}}); + index.emplace(object); + EXPECT_EQ(index.size(), 1); + + index.erase("random_id"); + EXPECT_EQ(index.size(), 1); + + EXPECT_TRUE(index.contains("id")); + EXPECT_EQ(index.find_by_id("id"), object.get()); + + auto items = index.find_by_tags(decltype(test_object::tags){{"tag", "value"}}); + EXPECT_EQ(items.size(), 1); + EXPECT_TRUE(items.contains(object.get())); +} + +} // namespace diff --git a/tests/unit/matcher/exact_match.cpp b/tests/unit/matcher/exact_match.cpp index d931fbc30..5b8b0f627 100644 --- a/tests/unit/matcher/exact_match.cpp +++ b/tests/unit/matcher/exact_match.cpp @@ -67,7 +67,7 @@ TEST(TestExactMatch, Expiration) std::chrono::system_clock::now().time_since_epoch()) .count(); - exact_match matcher(std::vector>{{"aaaa", now - 1}, + exact_match matcher(std::vector>{{"aaaa", now - 1}, {"bbbb", now + 100}, {"cccc", now - 1}, {"dddd", 0}, {"dddd", now - 1}, {"eeee", now - 1}, {"eeee", 0}, {"ffff", now + 100}, {"ffff", now}}); @@ -87,6 +87,58 @@ TEST(TestExactMatch, Expiration) EXPECT_TRUE(matcher.match("ffff").first); } +TEST(TestExactMatch, MultivectorConstructor) +{ + ddwaf::indexed_multivector> ivec; + ivec.emplace("vec1", {{"aaaa", 0}, {"bbbb", 0}, {"cccc", 0}, {"dddd", 0}}); + ivec.emplace("vec2", {{"eeee", 0}, {"ffff", 0}}); + exact_match matcher(ivec); + + EXPECT_STREQ(matcher.name().data(), "exact_match"); + EXPECT_STREQ(matcher.to_string().data(), ""); + + EXPECT_TRUE(matcher.match("aaaa").first); + EXPECT_TRUE(matcher.match("cccc").first); + + std::string_view input{"bbbb"}; + auto [res, highlight] = matcher.match(input); + EXPECT_TRUE(res); + EXPECT_STREQ(highlight.c_str(), input.data()); + + EXPECT_TRUE(matcher.match("dddd").first); + EXPECT_TRUE(matcher.match("eeee").first); + EXPECT_TRUE(matcher.match("ffff").first); + EXPECT_FALSE(matcher.match("gggg").first); +} + +TEST(TestExactMatch, MultivectorConstructorExpiration) +{ + uint64_t now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + ddwaf::indexed_multivector> ivec; + ivec.emplace("vec1", {{"aaaa", now - 1}, {"bbbb", now + 100}, {"cccc", now - 1}, {"dddd", 0}}); + ivec.emplace("vec2", {{"dddd", now - 1}, {"eeee", now - 1}}); + ivec.emplace("vec3", {{"eeee", 0}, {"ffff", now + 100}, {"ffff", now}}); + exact_match matcher(ivec); + + EXPECT_STREQ(matcher.name().data(), "exact_match"); + EXPECT_STREQ(matcher.to_string().data(), ""); + + EXPECT_FALSE(matcher.match("aaaa").first); + EXPECT_FALSE(matcher.match("cccc").first); + + std::string_view input{"bbbb"}; + auto [res, highlight] = matcher.match(input); + EXPECT_TRUE(res); + EXPECT_STREQ(highlight.c_str(), input.data()); + + EXPECT_TRUE(matcher.match("dddd").first); + EXPECT_TRUE(matcher.match("eeee").first); + EXPECT_TRUE(matcher.match("ffff").first); +} + TEST(TestExactMatch, InvalidMatchInput) { exact_match matcher({"aaaa", "bbbb", "cccc"}); diff --git a/tests/unit/matcher/ip_match_test.cpp b/tests/unit/matcher/ip_match_test.cpp index 77568cbcf..406dea28a 100644 --- a/tests/unit/matcher/ip_match_test.cpp +++ b/tests/unit/matcher/ip_match_test.cpp @@ -111,7 +111,7 @@ TEST(TestIPMatch, Expiration) std::chrono::system_clock::now().time_since_epoch()) .count(); - ip_match matcher(std::vector>{{"1.2.3.4", now - 1}, + ip_match matcher(std::vector>{{"1.2.3.4", now - 1}, {"5.6.7.254", now + 100}, {"::ffff:0102:0304", now - 1}, {"1234:0:0:0:0:0:0:5678", now + 100}, {"::1", now - 1}, {"abcd::1234:5678:1234:5678", now + 100}, {"abcd::1234:0:0:0", now - 1}, @@ -136,7 +136,7 @@ TEST(TestIPMatch, OverlappingExpiration) std::chrono::system_clock::now().time_since_epoch()) .count(); - ip_match matcher(std::vector>{{"4.4.4.4", 0}, + ip_match matcher(std::vector>{{"4.4.4.4", 0}, {"4.4.4.4", now - 1}, {"5.5.5.5", now - 1}, {"5.5.5.5", 0}, {"1.0.0.0/8", now - 1}, {"1.2.3.4", now + 100}, {"2.2.0.0/16", now + 100}, {"2.2.7.8", now - 100}, {"2.3.0.0/16", 0}, {"2.3.9.1", now - 100}, {"2.4.0.0/16", now - 1}, {"2.4.3.4", 0}}); @@ -160,4 +160,53 @@ TEST(TestIPMatch, OverlappingExpiration) EXPECT_TRUE(match(matcher, "2.4.3.4")); } +TEST(TestIPMatch, MultivectorConstructor) +{ + ddwaf::indexed_multivector> ivec; + ivec.emplace("vec1", {{"1.2.3.4", 0}, {"5.6.7.254", 0}, {"::ffff:0102:0304", 0}}); + ivec.emplace( + "vec2", {{"1234:0:0:0:0:0:0:5678", 0}, {"::1", 0}, {"abcd::1234:5678:1234:5678", 0}}); + ivec.emplace("vec3", {{"abcd::1234:0:0:0", 0}, {"abcd::1234:ffff:ffff:ffff", 0}}); + ip_match matcher(ivec); + + EXPECT_STREQ(matcher.to_string().data(), ""); + EXPECT_STREQ(matcher.name().data(), "ip_match"); + + EXPECT_TRUE(match(matcher, "1.2.3.4")); + EXPECT_TRUE(match(matcher, "5.6.7.254")); + EXPECT_TRUE(match(matcher, "::ffff:0102:0304")); + EXPECT_TRUE(match(matcher, "1234:0:0:0:0:0:0:5678")); + EXPECT_TRUE(match(matcher, "::1")); + EXPECT_TRUE(match(matcher, "abcd::1234:5678:1234:5678")); + EXPECT_TRUE(match(matcher, "abcd::1234:0:0:0")); + EXPECT_TRUE(match(matcher, "abcd::1234:ffff:ffff:ffff")); + EXPECT_FALSE(match(matcher, "3.4.2.1")); +} + +TEST(TestIPMatch, MultivectorConstructorExpiration) +{ + uint64_t now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + ddwaf::indexed_multivector> ivec; + ivec.emplace( + "vec1", {{"1.2.3.4", now - 1}, {"5.6.7.254", now + 100}, {"::ffff:0102:0304", now - 1}}); + ivec.emplace("vec2", {{"1234:0:0:0:0:0:0:5678", now + 100}, {"::1", now - 1}, + {"abcd::1234:5678:1234:5678", now + 100}}); + ivec.emplace("vec3", {{"abcd::1234:0:0:0", now - 1}, {"abcd::1234:ffff:ffff:ffff", now + 100}}); + ip_match matcher(ivec); + + EXPECT_STREQ(matcher.to_string().data(), ""); + EXPECT_STREQ(matcher.name().data(), "ip_match"); + + EXPECT_FALSE(match(matcher, "1.2.3.4")); + EXPECT_TRUE(match(matcher, "5.6.7.254")); + EXPECT_FALSE(match(matcher, "::ffff:0102:0304")); + EXPECT_TRUE(match(matcher, "1234:0:0:0:0:0:0:5678")); + EXPECT_FALSE(match(matcher, "::1")); + EXPECT_TRUE(match(matcher, "abcd::1234:5678:1234:5678")); + EXPECT_FALSE(match(matcher, "abcd::1234:0:0:0")); + EXPECT_TRUE(match(matcher, "abcd::1234:ffff:ffff:ffff")); +} + } // namespace diff --git a/tests/unit/parser_v2_actions_test.cpp b/tests/unit/parser_v2_actions_test.cpp deleted file mode 100644 index 670924040..000000000 --- a/tests/unit/parser_v2_actions_test.cpp +++ /dev/null @@ -1,856 +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 "common/gtest_utils.hpp" -#include "fmt/core.h" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2Actions, EmptyActions) -{ - auto object = yaml_to_object(R"([])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - - { - const auto &spec = actions->at("block"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "403"); - EXPECT_STR(parameters.at("grpc_status_code"), "10"); - EXPECT_STR(parameters.at("type"), "auto"); - } - - { - const auto &spec = actions->at("stack_trace"); - EXPECT_EQ(spec.type, action_type::generate_stack); - EXPECT_EQ(spec.type_str, "generate_stack"); - EXPECT_EQ(spec.parameters.size(), 0); - } - - { - const auto &spec = actions->at("extract_schema"); - EXPECT_EQ(spec.type, action_type::generate_schema); - EXPECT_EQ(spec.type_str, "generate_schema"); - EXPECT_EQ(spec.parameters.size(), 0); - } -} - -TEST(TestParserV2Actions, SingleAction) -{ - auto object = yaml_to_object(R"([{id: block_1, type: block_request, parameters: {}}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("block_1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("block_1")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); -} - -TEST(TestParserV2Actions, RedirectAction) -{ - std::vector> redirections{ - {"redirect_301", "301", "http://www.datadoghq.com"}, - {"redirect_302", "302", "http://www.datadoghq.com"}, - {"redirect_303", "303", "http://www.datadoghq.com"}, - {"redirect_307", "307", "http://www.datadoghq.com"}, - {"redirect_https", "303", "https://www.datadoghq.com"}, - {"redirect_path", "303", "/security/appsec"}, - }; - - std::string yaml; - yaml.append("["); - for (auto &[name, status_code, url] : redirections) { - yaml += fmt::format("{{id: {}, parameters: {{location: \"{}\", status_code: {}}}, type: " - "redirect_request}},", - name, url, status_code); - } - yaml.append("]"); - auto object = yaml_to_object(yaml); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 6); - EXPECT_NE(loaded.find("redirect_301"), loaded.end()); - EXPECT_NE(loaded.find("redirect_302"), loaded.end()); - EXPECT_NE(loaded.find("redirect_303"), loaded.end()); - EXPECT_NE(loaded.find("redirect_307"), loaded.end()); - EXPECT_NE(loaded.find("redirect_https"), loaded.end()); - EXPECT_NE(loaded.find("redirect_path"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 10); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - for (auto &[name, status_code, url] : redirections) { - ASSERT_TRUE(actions->contains(name)); - - const auto &spec = actions->at(name); - EXPECT_EQ(spec.type, action_type::redirect_request) << name; - EXPECT_EQ(spec.type_str, "redirect_request"); - EXPECT_EQ(spec.parameters.size(), 2); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), status_code.c_str()); - EXPECT_STR(parameters.at("location"), url.c_str()); - } -} - -TEST(TestParserV2Actions, RedirectActionInvalidStatusCode) -{ - auto object = yaml_to_object( - R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 404}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("redirect"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("redirect")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("redirect"); - EXPECT_EQ(spec.type, action_type::redirect_request); - EXPECT_EQ(spec.type_str, "redirect_request"); - EXPECT_EQ(spec.parameters.size(), 2); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "303"); - EXPECT_STR(parameters.at("location"), "http://www.google.com"); - } -} - -TEST(TestParserV2Actions, RedirectActionInvalid300StatusCode) -{ - auto object = yaml_to_object( - R"([{id: redirect, parameters: {location: "http://www.google.com", status_code: 304}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("redirect"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("redirect")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("redirect"); - EXPECT_EQ(spec.type, action_type::redirect_request); - EXPECT_EQ(spec.type_str, "redirect_request"); - EXPECT_EQ(spec.parameters.size(), 2); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "303"); - EXPECT_STR(parameters.at("location"), "http://www.google.com"); - } -} - -TEST(TestParserV2Actions, RedirectActionMissingStatusCode) -{ - auto object = yaml_to_object( - R"([{id: redirect, parameters: {location: "http://www.google.com"}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("redirect"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("redirect")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("redirect"); - EXPECT_EQ(spec.type, action_type::redirect_request); - EXPECT_EQ(spec.type_str, "redirect_request"); - EXPECT_EQ(spec.parameters.size(), 2); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "303"); - EXPECT_STR(parameters.at("location"), "http://www.google.com"); - } -} - -TEST(TestParserV2Actions, RedirectActionMissingLocation) -{ - auto object = yaml_to_object( - R"([{id: redirect, parameters: {status_code: 303}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("redirect"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("redirect")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("redirect"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "403"); - EXPECT_STR(parameters.at("grpc_status_code"), "10"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, RedirectActionNonHttpURL) -{ - auto object = yaml_to_object( - R"([{id: redirect, parameters: {status_code: 303, location: ftp://myftp.mydomain.com}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("redirect"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("redirect")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("redirect"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "403"); - EXPECT_STR(parameters.at("grpc_status_code"), "10"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, RedirectActionInvalidRelativePathURL) -{ - auto object = yaml_to_object( - R"([{id: redirect, parameters: {status_code: 303, location: ../../../etc/passwd}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("redirect"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("redirect")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("redirect"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "403"); - EXPECT_STR(parameters.at("grpc_status_code"), "10"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, OverrideDefaultBlockAction) -{ - auto object = yaml_to_object( - R"([{id: block, parameters: {location: "http://www.google.com", status_code: 302}, type: redirect_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("block"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("block"); - EXPECT_EQ(spec.type, action_type::redirect_request); - EXPECT_EQ(spec.type_str, "redirect_request"); - EXPECT_EQ(spec.parameters.size(), 2); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "302"); - EXPECT_STR(parameters.at("location"), "http://www.google.com"); - } -} - -TEST(TestParserV2Actions, BlockActionMissingStatusCode) -{ - auto object = yaml_to_object( - R"([{id: block, parameters: {type: "auto", grpc_status_code: 302}, type: block_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("block"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("block"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "403"); - EXPECT_STR(parameters.at("grpc_status_code"), "302"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, UnknownActionType) -{ - auto object = yaml_to_object( - R"([{id: sanitize, parameters: {location: "http://www.google.com", status_code: 302}, type: new_action_type}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("sanitize"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 5); - EXPECT_TRUE(actions->contains("sanitize")); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); -} - -TEST(TestParserV2Actions, BlockActionMissingGrpcStatusCode) -{ - auto object = yaml_to_object( - R"([{id: block, parameters: {type: "auto", status_code: 302}, type: block_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("block"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("block"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "302"); - EXPECT_STR(parameters.at("grpc_status_code"), "10"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, BlockActionMissingType) -{ - auto object = yaml_to_object( - R"([{id: block, parameters: {grpc_status_code: 11, status_code: 302}, type: block_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("block"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("block"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "302"); - EXPECT_STR(parameters.at("grpc_status_code"), "11"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, BlockActionMissingParameters) -{ - auto object = yaml_to_object(R"([{id: block, parameters: {}, type: block_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("block"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); - - { - const auto &spec = actions->at("block"); - EXPECT_EQ(spec.type, action_type::block_request); - EXPECT_EQ(spec.type_str, "block_request"); - EXPECT_EQ(spec.parameters.size(), 3); - - const auto ¶meters = spec.parameters; - EXPECT_STR(parameters.at("status_code"), "403"); - EXPECT_STR(parameters.at("grpc_status_code"), "10"); - EXPECT_STR(parameters.at("type"), "auto"); - } -} - -TEST(TestParserV2Actions, MissingID) -{ - auto object = yaml_to_object( - R"([{parameters: {location: "http://www.google.com", status_code: 302}, type: new_action_type}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); -} - -TEST(TestParserV2Actions, MissingType) -{ - auto object = yaml_to_object( - R"([{id: sanitize, parameters: {location: "http://www.google.com", status_code: 302}}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("sanitize"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("sanitize"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); -} - -TEST(TestParserV2Actions, MissingParameters) -{ - auto object = yaml_to_object(R"([{id: sanitize, type: sanitize_request}])"); - - ddwaf::ruleset_info::section_info section; - auto actions_array = static_cast(parameter(object)); - auto actions = parser::v2::parse_actions(actions_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("sanitize"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("missing key 'parameters'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("sanitize"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(actions->size(), 4); - EXPECT_TRUE(actions->contains("block")); - EXPECT_TRUE(actions->contains("stack_trace")); - EXPECT_TRUE(actions->contains("extract_schema")); - EXPECT_TRUE(actions->contains("monitor")); -} - -} // namespace diff --git a/tests/unit/parser_v2_data_test.cpp b/tests/unit/parser_v2_data_test.cpp deleted file mode 100644 index b2e880a90..000000000 --- a/tests/unit/parser_v2_data_test.cpp +++ /dev/null @@ -1,371 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2Data, ParseIPData) -{ - std::unordered_map data_ids{{"ip_data", "ip_match"}}; - - auto object = yaml_to_object( - R"([{id: ip_data, type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("ip_data"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 1); - EXPECT_STRV(data_cfg["ip_data"]->name(), "ip_match"); -} - -TEST(TestParserV2Data, ParseStringData) -{ - std::unordered_map data_ids{{"usr_data", "exact_match"}}; - - auto object = yaml_to_object( - R"([{id: usr_data, type: data_with_expiration, data: [{value: user, expiration: 500}]}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("usr_data"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 1); - EXPECT_STRV(data_cfg["usr_data"]->name(), "exact_match"); -} - -TEST(TestParserV2Data, ParseMultipleData) -{ - std::unordered_map data_ids{ - {"ip_data", "ip_match"}, {"usr_data", "exact_match"}}; - - auto object = yaml_to_object( - 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)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("ip_data"), loaded.end()); - EXPECT_NE(loaded.find("usr_data"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 2); - EXPECT_STRV(data_cfg["usr_data"]->name(), "exact_match"); - EXPECT_STRV(data_cfg["ip_data"]->name(), "ip_match"); -} - -TEST(TestParserV2Data, ParseUnknownDataID) -{ - std::unordered_map data_ids{{"usr_data", "exact_match"}}; - - auto object = yaml_to_object( - 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)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("ip_data"), loaded.end()); - EXPECT_NE(loaded.find("usr_data"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 2); - EXPECT_STRV(data_cfg["ip_data"]->name(), "ip_match"); - EXPECT_STRV(data_cfg["usr_data"]->name(), "exact_match"); -} - -TEST(TestParserV2Data, ParseUnsupportedTypes) -{ - std::unordered_map data_ids{ - {"usr_data", "match_regex"}, {"ip_data", "phrase_match"}}; - - auto object = yaml_to_object( - R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]},{id: ip_data, type: whatever, data: [{value: 192.168.1.1, expiration: 500}]}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 2); - EXPECT_NE(failed.find("ip_data"), failed.end()); - EXPECT_NE(failed.find("usr_data"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 2); - { - auto it = errors.find("matcher match_regex doesn't support dynamic data"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); - } - - { - auto it = errors.find("matcher phrase_match doesn't support dynamic data"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); - } - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 0); -} - -TEST(TestParserV2Data, ParseUnknownDataIDWithUnsupportedType) -{ - std::unordered_map data_ids{}; - - auto object = yaml_to_object( - R"([{id: usr_data, type: blob_with_expiration, data: [{value: user, expiration: 500}]}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("usr_data"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("failed to infer matcher"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("usr_data"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 0); -} - -TEST(TestParserV2Data, ParseMissingType) -{ - std::unordered_map data_ids{{"ip_data", "ip_match"}}; - - auto object = - yaml_to_object(R"([{id: ip_data, data: [{value: 192.168.1.1, expiration: 500}]}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ip_data"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 0); -} - -TEST(TestParserV2Data, ParseMissingID) -{ - std::unordered_map data_ids{{"ip_data", "ip_match"}}; - - auto object = yaml_to_object( - R"([{type: ip_with_expiration, data: [{value: 192.168.1.1, expiration: 500}]}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 0); -} - -TEST(TestParserV2Data, ParseMissingData) -{ - std::unordered_map data_ids{{"ip_data", "ip_match"}}; - - auto object = yaml_to_object(R"([{id: ip_data, type: ip_with_expiration}])"); - auto input = static_cast(parameter(object)); - - ddwaf::ruleset_info::section_info section; - auto data_cfg = parser::v2::parse_data(input, data_ids, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ip_data"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'data'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ip_data"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(data_cfg.size(), 0); -} -} // namespace diff --git a/tests/unit/parser_v2_input_filters_test.cpp b/tests/unit/parser_v2_input_filters_test.cpp deleted file mode 100644 index d3a7ecce8..000000000 --- a/tests/unit/parser_v2_input_filters_test.cpp +++ /dev/null @@ -1,671 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2InputFilters, ParseEmpty) -{ - ddwaf::object_limits limits; - auto object = yaml_to_object(R"([{id: 1, inputs: []}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("empty exclusion filter"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2InputFilters, ParseFilterWithoutID) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{inputs: [{address: http.client_ip}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2InputFilters, ParseDuplicateFilters) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}]}, {id: 1, inputs: [{address: usr.id}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("duplicate filter"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); -} - -TEST(TestParserV2InputFilters, ParseUnconditionalNoTargets) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 0); - EXPECT_TRUE(filter.filter); -} - -TEST(TestParserV2InputFilters, ParseUnconditionalTargetID) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2InputFilters, ParseUnconditionalTargetTags) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_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(TestParserV2InputFilters, ParseUnconditionalTargetPriority) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2InputFilters, ParseUnconditionalMultipleTargets) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}, {tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 2); - EXPECT_TRUE(filter.filter); - - { - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); - } - - { - const auto &target = filter.targets[1]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_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(TestParserV2InputFilters, ParseMultipleUnconditional) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}]}, {id: 2, inputs: [{address: usr.id}], rules_target: [{tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("1"), loaded.end()); - EXPECT_NE(loaded.find("2"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 2); - - { - const auto &filter_it = filters.input_filters.find("1"); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); - } - - { - const auto &filter_it = filters.input_filters.find("2"); - EXPECT_STR(filter_it->first, "2"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_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(TestParserV2InputFilters, ParseConditionalSingleCondition) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 1); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2InputFilters, ParseConditionalMultipleConditions) -{ - ddwaf::object_limits limits; - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], 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: .*}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); - - const auto &filter_it = filters.input_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 3); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_TRUE(filter.filter); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2InputFilters, IncompatibleMinVersion) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}], min_version: 99.0.0}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_NE(skipped.find("1"), skipped.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2InputFilters, IncompatibleMaxVersion) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, inputs: [{address: http.client_ip}], max_version: 0.0.99}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_NE(skipped.find("1"), skipped.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2InputFilters, CompatibleVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, inputs: [{address: http.client_ip}], min_version: 0.0.99, max_version: 2.0.0}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 1); -} - -} // namespace diff --git a/tests/unit/parser_v2_processors_test.cpp b/tests/unit/parser_v2_processors_test.cpp deleted file mode 100644 index bcbd7015a..000000000 --- a/tests/unit/parser_v2_processors_test.cpp +++ /dev/null @@ -1,622 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2Processors, ParseNoGenerator) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'generator'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseNoID) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseNoParameters) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1, generator: extract_schema}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'parameters'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseNoMappings) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1, generator: extract_schema, parameters: {}}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'mappings'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseEmptyMappings) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, generator: extract_schema, parameters: {mappings: []}}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("empty mappings"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseNoInput) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, generator: extract_schema, parameters: {mappings: [{}]}}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'inputs'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseEmptyInput) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [], output: out}]}}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("empty processor input mapping"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseNoOutput) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}]}]}}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'output'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseUnknownGenerator) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: unknown, parameters: {mappings: [{inputs: [{address: in}], output: out}]}}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("unknown generator 'unknown'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParseUseless) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: false, output: false}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("processor not used for evaluation or output"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(processors.size(), 0); -} - -TEST(TestParserV2Processors, ParsePreprocessor) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: true, output: false}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 1); - EXPECT_EQ(processors.pre.size(), 1); - EXPECT_EQ(processors.post.size(), 0); -} - -TEST(TestParserV2Processors, ParsePreprocessorWithOutput) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: true, output: true}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 1); - EXPECT_EQ(processors.pre.size(), 1); - EXPECT_EQ(processors.post.size(), 0); -} - -TEST(TestParserV2Processors, ParsePostprocessor) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: false, output: true}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 1); - EXPECT_EQ(processors.pre.size(), 0); - EXPECT_EQ(processors.post.size(), 1); -} - -TEST(TestParserV2Processors, ParseDuplicate) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: false, output: true},{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, evaluate: true, output: false}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 1); - EXPECT_EQ(processors.pre.size(), 0); - EXPECT_EQ(processors.post.size(), 1); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("duplicate processor"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } -} - -TEST(TestParserV2Processors, IncompatibleMinVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, min_version: 99.0.0, evaluate: false, output: true}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 0); - EXPECT_EQ(processors.pre.size(), 0); - EXPECT_EQ(processors.post.size(), 0); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_NE(skipped.find("1"), skipped.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } -} - -TEST(TestParserV2Processors, IncompatibleMaxVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, max_version: 0.0.99, evaluate: false, output: true}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 0); - EXPECT_EQ(processors.pre.size(), 0); - EXPECT_EQ(processors.post.size(), 0); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_NE(skipped.find("1"), skipped.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } -} - -TEST(TestParserV2Processors, CompatibleVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, generator: extract_schema, parameters: {mappings: [{inputs: [{address: in}], output: out}]}, min_version: 0.0.99, max_version: 2.0.0, evaluate: false, output: true}])"); - - ddwaf::ruleset_info::section_info section; - auto array = static_cast(parameter(object)); - auto processors = parser::v2::parse_processors(array, section, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(processors.size(), 1); - EXPECT_EQ(processors.pre.size(), 0); - EXPECT_EQ(processors.post.size(), 1); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } -} - -} // namespace diff --git a/tests/unit/parser_v2_rule_filters_test.cpp b/tests/unit/parser_v2_rule_filters_test.cpp deleted file mode 100644 index 0547d0630..000000000 --- a/tests/unit/parser_v2_rule_filters_test.cpp +++ /dev/null @@ -1,853 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2RuleFilters, ParseEmptyFilter) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("empty exclusion filter"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseFilterWithoutID) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{rules_target: [{rule_id: 2939}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseDuplicateUnconditional) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("duplicate filter"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseUnconditionalTargetID) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseUnconditionalTargetTags) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, rules_target: [{tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_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, ParseUnconditionalTargetPriority) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939, tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseUnconditionalMultipleTargets) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939},{tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 2); - - { - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); - } - - { - const auto &target = filter.targets[1]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_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, ParseMultipleUnconditional) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939}]},{id: 2, rules_target: [{tags: {type: rule, category: unknown}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("1"), loaded.end()); - EXPECT_NE(loaded.find("2"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 2); - EXPECT_EQ(filters.input_filters.size(), 0); - - { - const auto &filter_it = filters.rule_filters.find("1"); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); - } - - { - const auto &filter_it = filters.rule_filters.find("2"); - EXPECT_STR(filter_it->first, "2"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 0); - EXPECT_EQ(filter.targets.size(), 1); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_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, ParseDuplicateConditional) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - 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: .*}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseConditionalSingleCondition) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939}], conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 1); - EXPECT_EQ(filter.targets.size(), 1); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseConditionalGlobal) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 1); - EXPECT_EQ(filter.targets.size(), 0); - EXPECT_EQ(filter.on_match, exclusion::filter_mode::bypass); -} - -TEST(TestParserV2RuleFilters, ParseConditionalMultipleConditions) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - 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: .*}}]}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.expr->size(), 3); - EXPECT_EQ(filter.targets.size(), 1); - EXPECT_EQ(filter.on_match, exclusion::filter_mode::bypass); - - const auto &target = filter.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::id); - EXPECT_STR(target.ref_id, "2939"); - EXPECT_EQ(target.tags.size(), 0); -} - -TEST(TestParserV2RuleFilters, ParseOnMatchMonitor) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: monitor}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.on_match, exclusion::filter_mode::monitor); -} - -TEST(TestParserV2RuleFilters, ParseOnMatchBypass) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: bypass}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.on_match, exclusion::filter_mode::bypass); -} - -TEST(TestParserV2RuleFilters, ParseCustomOnMatch) -{ - ddwaf::object_limits limits; - - auto object = - yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: obliterate}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); - - const auto &filter_it = filters.rule_filters.begin(); - EXPECT_STR(filter_it->first, "1"); - - const auto &filter = filter_it->second; - EXPECT_EQ(filter.on_match, exclusion::filter_mode::custom); - EXPECT_STR(filter.custom_action, "obliterate"); -} - -TEST(TestParserV2RuleFilters, ParseInvalidOnMatch) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object(R"([{id: 1, rules_target: [{rule_id: 2939}], on_match: ""}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("empty on_match value"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, IncompatibleMinVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939}], min_version: 99.0.0, on_match: monitor}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_NE(skipped.find("1"), skipped.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, IncompatibleMaxVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939}], max_version: 0.0.99, on_match: monitor}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_NE(skipped.find("1"), skipped.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 0); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -TEST(TestParserV2RuleFilters, CompatibleVersion) -{ - ddwaf::object_limits limits; - - auto object = yaml_to_object( - R"([{id: 1, rules_target: [{rule_id: 2939}], min_version: 0.0.99, max_version: 2.0.0, on_match: monitor}])"); - - std::unordered_map data_ids; - ddwaf::ruleset_info::section_info section; - auto filters_array = static_cast(parameter(object)); - auto filters = parser::v2::parse_filters(filters_array, section, data_ids, limits); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(filters.rule_filters.size(), 1); - EXPECT_EQ(filters.input_filters.size(), 0); -} - -} // namespace diff --git a/tests/unit/parser_v2_rules_override_test.cpp b/tests/unit/parser_v2_rules_override_test.cpp deleted file mode 100644 index 5f4003995..000000000 --- a/tests/unit/parser_v2_rules_override_test.cpp +++ /dev/null @@ -1,324 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2RulesOverride, ParseRuleOverrideWithoutSideEffects) -{ - auto object = yaml_to_object(R"([{rules_target: [{tags: {confidence: 1}}]}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("rule override without side-effects"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } -} - -TEST(TestParserV2RulesOverride, ParseRuleOverrideWithoutTargets) -{ - auto object = yaml_to_object(R"([{rules_target: [{}], enabled: false}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("rule override with no targets"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } -} - -TEST(TestParserV2RulesOverride, ParseRuleOverride) -{ - auto object = - yaml_to_object(R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block]}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("index:0"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(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::reference_type::tags); - EXPECT_TRUE(target.ref_id.empty()); - EXPECT_EQ(target.tags.size(), 1); - EXPECT_STR(target.tags["confidence"], "1"); -} - -TEST(TestParserV2RulesOverride, ParseMultipleRuleOverrides) -{ - auto object = yaml_to_object( - R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block]},{rules_target: [{rule_id: 1}], enabled: false}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("index:0"), loaded.end()); - EXPECT_NE(loaded.find("index:1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(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::reference_type::tags); - EXPECT_TRUE(target.ref_id.empty()); - EXPECT_EQ(target.tags.size(), 1); - EXPECT_STR(target.tags["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::reference_type::id); - EXPECT_STR(target.ref_id, "1"); - EXPECT_EQ(target.tags.size(), 0); - } -} - -TEST(TestParserV2RulesOverride, ParseInconsistentRuleOverride) -{ - auto object = yaml_to_object( - R"([{rules_target: [{tags: {confidence: 1}}, {rule_id: 1}], on_match: [block], enabled: false}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("rule override targets rules and tags"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(overrides.by_ids.size(), 0); - EXPECT_EQ(overrides.by_tags.size(), 0); -} - -TEST(TestParserV2RulesOverride, ParseRuleOverrideForTags) -{ - auto object = yaml_to_object( - R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block], tags: {category: new_category, threshold: 25}}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("index:0"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(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); - EXPECT_EQ(ovrd.tags.size(), 2); - EXPECT_STR(ovrd.tags["category"], "new_category"); - EXPECT_STR(ovrd.tags["threshold"], "25"); - - auto &target = ovrd.targets[0]; - EXPECT_EQ(target.type, parser::reference_type::tags); - EXPECT_TRUE(target.ref_id.empty()); - EXPECT_EQ(target.tags.size(), 1); - EXPECT_STR(target.tags["confidence"], "1"); -} - -TEST(TestParserV2RulesOverride, ParseInvalidTagsField) -{ - auto object = yaml_to_object( - R"([{rules_target: [{tags: {confidence: 1}}], on_match: [block], tags: [{category: new_category}, {threshold: 25}]}])"); - - ddwaf::ruleset_info::section_info section; - auto override_array = static_cast(parameter(object)); - auto overrides = parser::v2::parse_overrides(override_array, section); - ddwaf_object_free(&object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - - auto it = errors.find("bad cast, expected 'map', obtained 'array'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(overrides.by_ids.size(), 0); - EXPECT_EQ(overrides.by_tags.size(), 0); -} - -} // namespace diff --git a/tests/unit/parser_v2_rules_test.cpp b/tests/unit/parser_v2_rules_test.cpp deleted file mode 100644 index 145080cd1..000000000 --- a/tests/unit/parser_v2_rules_test.cpp +++ /dev/null @@ -1,665 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2Rules, ParseRule) -{ - ddwaf::object_limits limits; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, 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, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 1); - EXPECT_NE(rules.find("1"), rules.end()); - - parser::rule_spec &rule = rules["1"]; - EXPECT_TRUE(rule.enabled); - EXPECT_EQ(rule.expr->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; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - 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, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'type'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, ParseRuleInvalidTransformer) -{ - ddwaf::object_limits limits; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - 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], transformers: [unknown]}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("invalid transformer unknown"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} -TEST(TestParserV2Rules, ParseRuleWithoutID) -{ - ddwaf::object_limits limits; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - 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, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, ParseMultipleRules) -{ - ddwaf::object_limits limits; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, 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, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("1"), loaded.end()); - EXPECT_NE(loaded.find("secondrule"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(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.expr->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.expr->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; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, 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, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 2); - EXPECT_NE(loaded.find("1"), loaded.end()); - EXPECT_NE(loaded.find("secondrule"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("error"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'conditions'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("error"), error_rules.end()); - - ddwaf_object_free(&root); - } - - 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.expr->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.expr->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; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, 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, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("1"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("duplicate rule"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - 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.expr->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, KeyPathTooLong) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x, y, z]}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("key_path beyond maximum container depth"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, NegatedMatcherTooManyParameters) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: "!match_regex", parameters: {inputs: [{address: arg1}, {address: arg2}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("1"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("multiple targets for non-variadic argument"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("1"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, SupportedVersionedOperator) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v2"}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_TRUE(loaded.contains("rsp-930-003")); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 1); -} - -TEST(TestParserV2Rules, UnsupportedVersionedOperator) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{"id":"rsp-930-003","name":"SQLi Exploit detection","tags":{"type":"sqli","category":"exploit_detection","module":"rasp"},"conditions":[{"parameters":{"resource":[{"address":"server.db.statement"}],"params":[{"address":"server.request.query"},{"address":"server.request.body"},{"address":"server.request.path_params"},{"address":"grpc.server.request.message"},{"address":"graphql.server.all_resolvers"},{"address":"graphql.server.resolver"}],"db_type":[{"address":"server.db.system"}]},"operator":"sqli_detector@v20"}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_TRUE(skipped.contains("rsp-930-003")); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, IncompatibleMinVersion) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 99.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_TRUE(skipped.contains("1")); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, IncompatibleMaxVersion) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, max_version: 0.0.99, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_TRUE(skipped.contains("1")); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 0); -} - -TEST(TestParserV2Rules, CompatibleVersion) -{ - ddwaf::object_limits limits; - limits.max_container_depth = 2; - ddwaf::ruleset_info::section_info section; - std::unordered_map rule_data_ids; - - auto rule_object = yaml_to_object( - R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, min_version: 0.0.99, max_version: 2.0.0, conditions: [{operator: match_regex, parameters: {inputs: [{address: arg1}], regex: .*}}]}])"); - - auto rule_array = static_cast(parameter(rule_object)); - EXPECT_EQ(rule_array.size(), 1); - - auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); - ddwaf_object_free(&rule_object); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_TRUE(loaded.contains("1")); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(rules.size(), 1); -} - -} // namespace diff --git a/tests/unit/parser_v2_scanner_test.cpp b/tests/unit/parser_v2_scanner_test.cpp deleted file mode 100644 index b162d02da..000000000 --- a/tests/unit/parser_v2_scanner_test.cpp +++ /dev/null @@ -1,645 +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 "common/gtest_utils.hpp" -#include "parser/common.hpp" -#include "parser/parser.hpp" - -using namespace ddwaf; - -namespace { - -TEST(TestParserV2Scanner, ParseKeyOnlyScanner) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("ecd"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 1); - EXPECT_NE(scanners.find_by_id("ecd"), nullptr); - - const auto *scnr = scanners.find_by_id("ecd"); - EXPECT_STREQ(scnr->get_id().data(), "ecd"); - std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; - EXPECT_EQ(scnr->get_tags(), tags); - - ddwaf_object value; - ddwaf_object_string(&value, "dog@datadoghq.com"); - EXPECT_TRUE(scnr->eval("email", value)); - EXPECT_FALSE(scnr->eval("mail", value)); - ddwaf_object_free(&value); - - ddwaf_object_string(&value, "ansodinsod"); - EXPECT_TRUE(scnr->eval("email", value)); - ddwaf_object_free(&value); -} - -TEST(TestParserV2Scanner, ParseValueOnlyScanner) -{ - auto definition = json_to_object( - R"([{"id":"ecd","value":{"operator":"match_regex","parameters":{"regex":"@"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("ecd"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 1); - EXPECT_NE(scanners.find_by_id("ecd"), nullptr); - - const auto *scnr = scanners.find_by_id("ecd"); - EXPECT_STREQ(scnr->get_id().data(), "ecd"); - std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; - EXPECT_EQ(scnr->get_tags(), tags); - - ddwaf_object value; - ddwaf_object_string(&value, "dog@datadoghq.com"); - EXPECT_TRUE(scnr->eval("email", value)); - EXPECT_TRUE(scnr->eval("mail", value)); - ddwaf_object_free(&value); - - ddwaf_object_string(&value, "ansodinsod"); - EXPECT_FALSE(scnr->eval("email", value)); - ddwaf_object_free(&value); -} - -TEST(TestParserV2Scanner, ParseKeyValueScanner) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"@"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("ecd"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 1); - EXPECT_NE(scanners.find_by_id("ecd"), nullptr); - - const auto *scnr = scanners.find_by_id("ecd"); - EXPECT_STREQ(scnr->get_id().data(), "ecd"); - std::unordered_map tags{{"type", "email"}, {"category", "pii"}}; - EXPECT_EQ(scnr->get_tags(), tags); - - ddwaf_object value; - ddwaf_object_string(&value, "dog@datadoghq.com"); - EXPECT_TRUE(scnr->eval("email", value)); - EXPECT_FALSE(scnr->eval("mail", value)); - ddwaf_object_free(&value); - - ddwaf_object_string(&value, "ansodinsod"); - EXPECT_FALSE(scnr->eval("email", value)); - ddwaf_object_free(&value); -} - -TEST(TestParserV2Scanner, ParseNoID) -{ - auto definition = json_to_object( - R"([{"key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"@"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("index:0"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'id'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("index:0"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseNoTags) -{ - auto definition = json_to_object( - R"([{"id":"error","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"@"}}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("error"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'tags'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("error"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseNoKeyValue) -{ - auto definition = - json_to_object(R"([{"id":"error","tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("error"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("scanner has no key or value matcher"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("error"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseDuplicate) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}},{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_NE(loaded.find("ecd"), loaded.end()); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("duplicate scanner"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 1); -} - -TEST(TestParserV2Scanner, ParseKeyNoOperator) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"parameters":{"regex":"email"}},"value":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'operator'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseKeyNoParameters) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex"},"value":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'parameters'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseValueNoOperator) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'operator'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseValueNoParameters) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"value":{"operator":"match_regex"},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("missing key 'parameters'"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseUnknownMatcher) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"what","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("unknown matcher: what"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, ParseRuleDataID) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"exact_match","parameters":{"data":"invalid"}},"tags":{"type":"email","category":"pii"}}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 1); - EXPECT_NE(failed.find("ecd"), failed.end()); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 1); - auto it = errors.find("dynamic data on scanner condition"); - EXPECT_NE(it, errors.end()); - - auto error_rules = static_cast(it->second); - EXPECT_EQ(error_rules.size(), 1); - EXPECT_NE(error_rules.find("ecd"), error_rules.end()); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, IncompatibleMinVersion) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "min_version": "99.0.0"}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_TRUE(skipped.contains("ecd")); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, IncompatibleMaxVersion) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "max_version": "0.0.99"}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 0); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 1); - EXPECT_TRUE(skipped.contains("ecd")); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 0); -} - -TEST(TestParserV2Scanner, CompatibleVersion) -{ - auto definition = json_to_object( - R"([{"id":"ecd","key":{"operator":"match_regex","parameters":{"regex":"email"}},"tags":{"type":"email","category":"pii"}, "min_version": "0.0.99", "max_version": "2.0.0"}])"); - auto scanners_array = static_cast(parameter(definition)); - - ddwaf::ruleset_info::section_info section; - auto scanners = parser::v2::parse_scanners(scanners_array, section); - ddwaf_object_free(&definition); - - { - ddwaf::parameter root; - section.to_object(root); - - auto root_map = static_cast(root); - - auto loaded = ddwaf::parser::at(root_map, "loaded"); - EXPECT_EQ(loaded.size(), 1); - EXPECT_TRUE(loaded.contains("ecd")); - - auto failed = ddwaf::parser::at(root_map, "failed"); - EXPECT_EQ(failed.size(), 0); - - auto skipped = ddwaf::parser::at(root_map, "skipped"); - EXPECT_EQ(skipped.size(), 0); - - auto errors = ddwaf::parser::at(root_map, "errors"); - EXPECT_EQ(errors.size(), 0); - - ddwaf_object_free(&root); - } - - EXPECT_EQ(scanners.size(), 1); -} - -} // namespace diff --git a/tests/unit/ruleset_info_test.cpp b/tests/unit/ruleset_info_test.cpp index d712fd893..33f83812f 100644 --- a/tests/unit/ruleset_info_test.cpp +++ b/tests/unit/ruleset_info_test.cpp @@ -4,7 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include "parser/common.hpp" +#include "configuration/common/common.hpp" #include "ruleset_info.hpp" #include "common/gtest_utils.hpp" @@ -53,27 +53,27 @@ TEST(TestRulesetInfo, ValidRulesetInfo) auto root_map = static_cast(root); EXPECT_EQ(root_map.size(), 4); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "2.3.4"); std::unordered_map kv{ {"rules", "first"}, {"exclusions", "second"}, {"rules_override", "third"}}; for (auto &[key, value] : kv) { - auto section = ddwaf::parser::at(root_map, key); + auto section = ddwaf::at(root_map, key); EXPECT_EQ(section.size(), 4); - auto loaded = ddwaf::parser::at(section, "loaded"); + auto loaded = ddwaf::at(section, "loaded"); EXPECT_EQ(loaded.size(), 1); EXPECT_STREQ(static_cast(loaded[0]).c_str(), value.c_str()); - auto failed = ddwaf::parser::at(section, "failed"); + auto failed = ddwaf::at(section, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(section, "skipped"); + auto skipped = ddwaf::at(section, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(section, "errors"); + auto errors = ddwaf::at(section, "errors"); EXPECT_EQ(errors.size(), 0); } @@ -100,16 +100,16 @@ TEST(TestRulesetInfo, FailedWithErrorsRulesetInfo) auto root_map = static_cast(root); EXPECT_EQ(root_map.size(), 2); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "2.3.4"); - auto section = ddwaf::parser::at(root_map, "rules"); + auto section = ddwaf::at(root_map, "rules"); EXPECT_EQ(section.size(), 4); - auto loaded = ddwaf::parser::at(section, "loaded"); + auto loaded = ddwaf::at(section, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(section, "failed"); + auto failed = ddwaf::at(section, "failed"); EXPECT_EQ(failed.size(), 5); EXPECT_NE(failed.find("first"), failed.end()); EXPECT_NE(failed.find("second"), failed.end()); @@ -117,10 +117,10 @@ TEST(TestRulesetInfo, FailedWithErrorsRulesetInfo) EXPECT_NE(failed.find("fourth"), failed.end()); EXPECT_NE(failed.find("fifth"), failed.end()); - auto skipped = ddwaf::parser::at(section, "skipped"); + auto skipped = ddwaf::at(section, "skipped"); EXPECT_EQ(skipped.size(), 0); - auto errors = ddwaf::parser::at(section, "errors"); + auto errors = ddwaf::at(section, "errors"); EXPECT_EQ(errors.size(), 3); { auto it = errors.find("error1"); @@ -174,22 +174,22 @@ TEST(TestRulesetInfo, SkippedRulesetInfo) auto root_map = static_cast(root); EXPECT_EQ(root_map.size(), 2); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "2.3.4"); - auto section = ddwaf::parser::at(root_map, "rules"); + auto section = ddwaf::at(root_map, "rules"); EXPECT_EQ(section.size(), 4); - auto loaded = ddwaf::parser::at(section, "loaded"); + auto loaded = ddwaf::at(section, "loaded"); EXPECT_EQ(loaded.size(), 0); - auto failed = ddwaf::parser::at(section, "failed"); + auto failed = ddwaf::at(section, "failed"); EXPECT_EQ(failed.size(), 0); - auto skipped = ddwaf::parser::at(section, "skipped"); + auto skipped = ddwaf::at(section, "skipped"); EXPECT_EQ(skipped.size(), 5); - auto errors = ddwaf::parser::at(section, "errors"); + auto errors = ddwaf::at(section, "errors"); EXPECT_EQ(errors.size(), 0); ddwaf_object_free(&root); @@ -214,13 +214,13 @@ TEST(TestRulesetInfo, SectionErrorRulesetInfo) auto root_map = static_cast(root); EXPECT_EQ(root_map.size(), 2); - auto version = ddwaf::parser::at(root_map, "ruleset_version"); + auto version = ddwaf::at(root_map, "ruleset_version"); EXPECT_STREQ(version.c_str(), "2.3.4"); - auto section = ddwaf::parser::at(root_map, "rules_data"); + auto section = ddwaf::at(root_map, "rules_data"); EXPECT_EQ(section.size(), 1); - auto error = ddwaf::parser::at(section, "error"); + auto error = ddwaf::at(section, "error"); EXPECT_STR(error, "expected 'array' found 'map'"); } diff --git a/tests/unit/ruleset_test.cpp b/tests/unit/ruleset_test.cpp index f53cd79d0..1ca3bd3a8 100644 --- a/tests/unit/ruleset_test.cpp +++ b/tests/unit/ruleset_test.cpp @@ -35,10 +35,6 @@ TEST(TestRuleset, InsertSingleRegularBaseRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - /* //EXPECT_EQ(ruleset.base/g_collections.size(), 3);*/ - /*//EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0);*/ - /*//EXPECT_EQ(ruleset.user/g_collections.size(), 0);*/ - /*//EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0);*/ } { @@ -46,10 +42,6 @@ TEST(TestRuleset, InsertSingleRegularBaseRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - /* //EXPECT_EQ(ruleset.base/g_collections.size(), 3);*/ - /*//EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0);*/ - /*//EXPECT_EQ(ruleset.user/g_collections.size(), 0);*/ - /*//EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0);*/ } } @@ -69,10 +61,6 @@ TEST(TestRuleset, InsertSinglePriorityBaseRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - ////EXPECT_EQ(ruleset.base/g_collections.size(), 0); - ////EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); - ////EXPECT_EQ(ruleset.user/g_collections.size(), 0); - ////EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { @@ -80,10 +68,6 @@ TEST(TestRuleset, InsertSinglePriorityBaseRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - ////EXPECT_EQ(ruleset.base/g_collections.size(), 0); - ////EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); - ////EXPECT_EQ(ruleset.user/g_collections.size(), 0); - ////EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } @@ -103,10 +87,6 @@ TEST(TestRuleset, InsertSingleMixedBaseRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 3); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); - // EXPECT_EQ(ruleset.user/g_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { @@ -114,10 +94,6 @@ TEST(TestRuleset, InsertSingleMixedBaseRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 3); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); - // EXPECT_EQ(ruleset.user/g_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } @@ -143,10 +119,6 @@ TEST(TestRuleset, InsertSingleRegularUserRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { @@ -155,10 +127,6 @@ TEST(TestRuleset, InsertSingleRegularUserRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } @@ -183,10 +151,6 @@ TEST(TestRuleset, InsertSinglePriorityUserRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 3); } { @@ -194,10 +158,6 @@ TEST(TestRuleset, InsertSinglePriorityUserRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 3); } } @@ -223,10 +183,6 @@ TEST(TestRuleset, InsertSingleMixedUserRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } { @@ -234,10 +190,6 @@ TEST(TestRuleset, InsertSingleMixedUserRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } } @@ -263,10 +215,6 @@ TEST(TestRuleset, InsertSingleRegularMixedRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 3); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 2); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } { @@ -274,10 +222,6 @@ TEST(TestRuleset, InsertSingleRegularMixedRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 3); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_collections.size(), 2); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 0); } } @@ -302,10 +246,6 @@ TEST(TestRuleset, InsertSinglePriorityMixedRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } { @@ -313,10 +253,6 @@ TEST(TestRuleset, InsertSinglePriorityMixedRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 6); - // EXPECT_EQ(ruleset.base/g_collections.size(), 0); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_collections.size(), 0); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } } @@ -348,10 +284,6 @@ TEST(TestRuleset, InsertSingleMixedMixedRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 12); - // EXPECT_EQ(ruleset.base/g_collections.size(), 3); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); - // EXPECT_EQ(ruleset.user/g_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } { @@ -359,10 +291,6 @@ TEST(TestRuleset, InsertSingleMixedMixedRules) ruleset.insert_rules(rules, {}); EXPECT_EQ(ruleset.rules.size(), 12); - // EXPECT_EQ(ruleset.base/g_collections.size(), 3); - // EXPECT_EQ(ruleset.base/g_priority_collections.size(), 2); - // EXPECT_EQ(ruleset.user/g_collections.size(), 3); - // EXPECT_EQ(ruleset.user/g_priority_collections.size(), 2); } } diff --git a/tests/unit/sha256_test.cpp b/tests/unit/sha256_test.cpp index 815f2f6c9..68e09375c 100644 --- a/tests/unit/sha256_test.cpp +++ b/tests/unit/sha256_test.cpp @@ -4,12 +4,12 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include "sha256.hpp" - +#include #include #include #include "common/gtest_utils.hpp" +#include "sha256.hpp" TEST(TestSha256, RandomInputTest) { diff --git a/tests/unit/waf_test.cpp b/tests/unit/waf_test.cpp index 2051872d1..d90d7ead5 100644 --- a/tests/unit/waf_test.cpp +++ b/tests/unit/waf_test.cpp @@ -4,6 +4,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. +#include "builder/waf_builder.hpp" #include "common/gtest_utils.hpp" #include "waf.hpp" @@ -13,15 +14,31 @@ namespace { constexpr std::string_view base_dir = "unit"; -TEST(TestWaf, RootAddresses) +ddwaf::waf build_instance(std::string_view rule_file) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + auto object = read_file(rule_file, base_dir); + if (object.type == DDWAF_OBJ_INVALID) { + throw std::runtime_error("Invalid ruleset object"); + } + + parameter ruleset = object; + parameter::map ruleset_map = static_cast(ruleset); + waf_builder builder{object_limits{}, ddwaf_object_free, std::make_shared()}; ddwaf::null_ruleset_info info; - ddwaf::waf instance{ - rule, info, ddwaf::object_limits(), ddwaf_object_free, std::make_shared()}; - ddwaf_object_free(&rule); + auto res = builder.add_or_update("default", ruleset_map, info); + ddwaf_object_free(&object); + + if (!res) { + throw std::runtime_error("Failed to load ruleset"); + } + + return builder.build(); +} + +TEST(TestWaf, RootAddresses) +{ + auto instance = build_instance("interface.yaml"); std::set available_addresses{"value1", "value2"}; for (const auto *address : instance.get_root_addresses()) { @@ -31,13 +48,7 @@ TEST(TestWaf, RootAddresses) TEST(TestWaf, BasicContextRun) { - auto rule = read_file("interface.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::null_ruleset_info info; - ddwaf::waf instance{ - rule, info, ddwaf::object_limits(), ddwaf_object_free, std::make_shared()}; - ddwaf_object_free(&rule); + auto instance = build_instance("interface.yaml"); ddwaf_object root; ddwaf_object tmp; @@ -49,19 +60,6 @@ TEST(TestWaf, BasicContextRun) delete ctx; } -TEST(TestWaf, RuleDisabledInRuleset) -{ - auto rule = read_file("rule_disabled.yaml", base_dir); - ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); - - ddwaf::null_ruleset_info info; - EXPECT_THROW((ddwaf::waf{rule, info, ddwaf::object_limits(), ddwaf_object_free, - std::make_shared()}), - ddwaf::parsing_error); - - ddwaf_object_free(&rule); -} - TEST(TestWaf, AddressUniqueness) { std::array addresses{"grpc.server.method", "grpc.server.request.message", diff --git a/tools/waf_runner.cpp b/tools/waf_runner.cpp index cae97e31a..0a799a10e 100644 --- a/tools/waf_runner.cpp +++ b/tools/waf_runner.cpp @@ -64,108 +64,101 @@ int main(int argc, char *argv[]) const std::vector rulesets = args["--ruleset"]; const std::vector inputs = args["--input"]; - if (rulesets.empty() || inputs.empty()) { - std::cout << "Usage: " << argv[0] << " --ruleset [..]" + if (rulesets.empty() || rulesets.size() > 1 || inputs.empty()) { + std::cout << "Usage: " << argv[0] << " --ruleset " << " --input [..]\n"; return EXIT_FAILURE; } - ddwaf_handle handle = nullptr; - for (const auto &ruleset : rulesets) { - auto rule = YAML::Load(read_file(ruleset)).as(); - if (handle == nullptr) { - const ddwaf_config config{{0, 0, 0}, {key_regex, value_regex}, ddwaf_object_free}; - handle = ddwaf_init(&rule, &config, nullptr); - } else { - auto *updated_handle = ddwaf_update(handle, &rule, nullptr); - ddwaf_destroy(handle); - handle = updated_handle; - } + const auto &ruleset = rulesets[0]; - ddwaf_object_free(&rule); - if (handle == nullptr) { - std::cout << "Failed to load " << ruleset << '\n'; - return EXIT_FAILURE; - } + auto rule = YAML::Load(read_file(ruleset)).as(); + const ddwaf_config config{{0, 0, 0}, {key_regex, value_regex}, ddwaf_object_free}; + ddwaf_handle handle = ddwaf_init(&rule, &config, nullptr); + ddwaf_object_free(&rule); + if (handle == nullptr) { + std::cout << "Failed to load " << ruleset << '\n'; + return EXIT_FAILURE; + } - std::cout << "-- Run with " << ruleset << '\n'; + std::cout << "-- Run with " << ruleset << '\n'; - ddwaf_context context = ddwaf_context_init(handle); - if (context == nullptr) { - ddwaf_destroy(handle); - std::cout << "Failed to initialise context\n"; - return EXIT_FAILURE; - } + ddwaf_context context = ddwaf_context_init(handle); + if (context == nullptr) { + ddwaf_destroy(handle); + std::cout << "Failed to initialise context\n"; + return EXIT_FAILURE; + } - for (const auto &json_str : inputs) { + for (const auto &json_str : inputs) { - std::cout << "---- Run with " << json_str << '\n'; - auto input = YAML::Load(json_str); + std::cout << "---- Run with " << json_str << '\n'; + auto input = YAML::Load(json_str); - ddwaf_object persistent; - ddwaf_object ephemeral; + ddwaf_object persistent; + ddwaf_object ephemeral; - auto persistent_input = input["persistent"]; - auto ephemeral_input = input["ephemeral"]; - if (!persistent_input.IsDefined() && !ephemeral_input.IsDefined()) { - persistent = input.as(); - ddwaf_object_map(&ephemeral); + auto persistent_input = input["persistent"]; + auto ephemeral_input = input["ephemeral"]; + if (!persistent_input.IsDefined() && !ephemeral_input.IsDefined()) { + persistent = input.as(); + ddwaf_object_map(&ephemeral); + } else { + if (input["persistent"].IsDefined()) { + persistent = input["persistent"].as(); } else { - if (input["persistent"].IsDefined()) { - persistent = input["persistent"].as(); - } else { - ddwaf_object_map(&persistent); - } - - if (input["ephemeral"].IsDefined()) { - ephemeral = input["ephemeral"].as(); - } else { - ddwaf_object_map(&ephemeral); - } + ddwaf_object_map(&persistent); } - ddwaf_result ret; - auto code = - ddwaf_run(context, &persistent, &ephemeral, &ret, std::numeric_limits::max()); - if (code == DDWAF_MATCH && ddwaf_object_size(&ret.events) > 0) { - std::stringstream ss; - YAML::Emitter out(ss); - out.SetIndent(2); - out.SetMapFormat(YAML::Block); - out.SetSeqFormat(YAML::Block); - out << object_to_yaml(ret.events); - - std::cout << "Events:\n" << ss.str() << "\n\n"; + if (input["ephemeral"].IsDefined()) { + ephemeral = input["ephemeral"].as(); + } else { + ddwaf_object_map(&ephemeral); } + } - if (code == DDWAF_MATCH && ddwaf_object_size(&ret.actions) > 0) { - std::stringstream ss; - YAML::Emitter out(ss); - out.SetIndent(2); - out.SetMapFormat(YAML::Block); - out.SetSeqFormat(YAML::Block); - out << object_to_yaml(ret.actions); + ddwaf_result ret; + auto code = + ddwaf_run(context, &persistent, &ephemeral, &ret, std::numeric_limits::max()); + if (code == DDWAF_MATCH && ddwaf_object_size(&ret.events) > 0) { + std::stringstream ss; + YAML::Emitter out(ss); + out.SetIndent(2); + out.SetMapFormat(YAML::Block); + out.SetSeqFormat(YAML::Block); + out << object_to_yaml(ret.events); + + std::cout << "Events:\n" << ss.str() << "\n\n"; + } - std::cout << "Actions:\n" << ss.str() << "\n\n"; - } + if (code == DDWAF_MATCH && ddwaf_object_size(&ret.actions) > 0) { + std::stringstream ss; + YAML::Emitter out(ss); + out.SetIndent(2); + out.SetMapFormat(YAML::Block); + out.SetSeqFormat(YAML::Block); + out << object_to_yaml(ret.actions); - if (ddwaf_object_size(&ret.derivatives) > 0) { - std::stringstream ss; - YAML::Emitter out(ss); - out.SetIndent(2); - out.SetMapFormat(YAML::Block); - out.SetSeqFormat(YAML::Block); - out << object_to_yaml(ret.derivatives); + std::cout << "Actions:\n" << ss.str() << "\n\n"; + } - std::cout << "Derivatives:\n" << ss.str() << "\n\n"; - } + if (ddwaf_object_size(&ret.derivatives) > 0) { + std::stringstream ss; + YAML::Emitter out(ss); + out.SetIndent(2); + out.SetMapFormat(YAML::Block); + out.SetSeqFormat(YAML::Block); + out << object_to_yaml(ret.derivatives); - ddwaf_result_free(&ret); + std::cout << "Derivatives:\n" << ss.str() << "\n\n"; } - ddwaf_context_destroy(context); + std::cout << "Total time: " << ret.total_runtime << '\n'; + ddwaf_result_free(&ret); } + ddwaf_context_destroy(context); + ddwaf_destroy(handle); return EXIT_SUCCESS;