From 6ea002cd14e81b4777e9358a7ab43ccd23e588bc Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Wed, 2 Oct 2024 15:18:39 +0100 Subject: [PATCH 1/8] Use sidecar Remote Config impl in AppSec helper --- .circleci/continue_config.yml | 13 +- appsec/cmake/helper.cmake | 4 +- appsec/src/extension/backtrace.c | 3 +- appsec/src/extension/commands/client_init.c | 91 +- appsec/src/extension/commands/config_sync.c | 22 +- appsec/src/extension/commands/config_sync.h | 7 +- appsec/src/extension/ddappsec.c | 35 +- appsec/src/extension/ddappsec.h | 3 + appsec/src/extension/ddappsec.version | 1 + appsec/src/extension/ddtrace.c | 20 + appsec/src/extension/ddtrace.h | 2 + appsec/src/extension/helper_process.c | 3 +- appsec/src/extension/helper_process.h | 7 +- appsec/src/extension/msgpack_helpers.c | 5 +- appsec/src/extension/request_lifecycle.c | 45 +- appsec/src/helper/client.cpp | 63 +- appsec/src/helper/client.hpp | 6 +- appsec/src/helper/engine.cpp | 45 +- appsec/src/helper/engine.hpp | 34 +- appsec/src/helper/engine_settings.hpp | 25 +- appsec/src/helper/json_helper.cpp | 13 +- appsec/src/helper/json_helper.hpp | 3 +- appsec/src/helper/main.cpp | 10 +- appsec/src/helper/network/proto.hpp | 15 +- appsec/src/helper/rate_limit.hpp | 9 +- appsec/src/helper/remote_config/client.cpp | 324 ++-- appsec/src/helper/remote_config/client.hpp | 82 +- .../helper/remote_config/client_handler.cpp | 106 +- .../helper/remote_config/client_handler.hpp | 62 +- appsec/src/helper/remote_config/config.cpp | 118 ++ appsec/src/helper/remote_config/config.hpp | 70 +- appsec/src/helper/remote_config/http_api.cpp | 147 -- appsec/src/helper/remote_config/http_api.hpp | 47 - .../listeners/asm_features_listener.cpp | 11 +- .../listeners/asm_features_listener.hpp | 10 +- .../config_aggregators/asm_aggregator.cpp | 7 +- .../config_aggregators/asm_aggregator.hpp | 8 +- .../asm_data_aggregator.cpp | 10 +- .../asm_data_aggregator.hpp | 6 +- .../config_aggregators/asm_dd_aggregator.cpp | 6 +- .../config_aggregators/asm_dd_aggregator.hpp | 6 +- .../config_aggregators/config_aggregator.hpp | 8 +- .../listeners/engine_listener.cpp | 27 +- .../listeners/engine_listener.hpp | 34 +- .../remote_config/listeners/listener.hpp | 11 +- appsec/src/helper/remote_config/product.cpp | 83 - appsec/src/helper/remote_config/product.hpp | 82 +- .../protocol/cached_target_file_hash.hpp | 22 - .../protocol/cached_target_files.hpp | 27 - .../helper/remote_config/protocol/client.hpp | 70 - .../remote_config/protocol/client_state.hpp | 30 - .../remote_config/protocol/client_tracer.hpp | 31 - .../remote_config/protocol/config_state.hpp | 32 - .../helper/remote_config/protocol/path.hpp | 26 - .../remote_config/protocol/target_file.hpp | 23 - .../helper/remote_config/protocol/targets.hpp | 28 - .../protocol/tuf/get_configs_request.hpp | 28 - .../protocol/tuf/get_configs_response.hpp | 24 - .../protocol/tuf/info_response.hpp | 22 - .../remote_config/protocol/tuf/parser.cpp | 357 ----- .../remote_config/protocol/tuf/parser.hpp | 86 -- .../remote_config/protocol/tuf/serializer.cpp | 171 --- .../remote_config/protocol/tuf/serializer.hpp | 19 - appsec/src/helper/remote_config/settings.hpp | 46 +- appsec/src/helper/runner.cpp | 91 ++ appsec/src/helper/runner.hpp | 10 +- appsec/src/helper/service.cpp | 29 +- appsec/src/helper/service.hpp | 32 +- appsec/src/helper/service_identifier.hpp | 55 - appsec/src/helper/service_manager.cpp | 42 +- appsec/src/helper/service_manager.hpp | 51 +- appsec/src/helper/subscriber/base.hpp | 8 +- appsec/src/helper/subscriber/waf.cpp | 28 +- appsec/src/helper/subscriber/waf.hpp | 13 +- appsec/src/helper/utils.hpp | 18 + .../tests/extension/report_backtrace_01.phpt | 2 +- .../extension/rinit_agent_host_port_01.phpt | 30 - .../extension/rinit_agent_host_port_02.phpt | 27 - .../extension/rinit_agent_host_port_03.phpt | 27 - .../extension/rinit_agent_host_port_04.phpt | 32 - .../extension/rinit_agent_host_port_05.phpt | 30 - .../extension/rinit_agent_host_port_06.phpt | 29 - .../extension/rinit_agent_host_port_07.phpt | 29 - .../extension/rinit_agent_host_port_08.phpt | 30 - .../extension/rinit_rshutdown_basic.phpt | Bin 5562 -> 4867 bytes appsec/tests/helper/CMakeLists.txt | 4 + appsec/tests/helper/broker_test.cpp | 47 +- appsec/tests/helper/client_test.cpp | 31 +- appsec/tests/helper/engine_test.cpp | 208 +-- .../remote_config/client_handler_test.cpp | 331 ---- .../helper/remote_config/client_test.cpp | 1348 ----------------- .../listeners/asm_features_listener_test.cpp | 34 +- .../asm_aggregator_test.cpp | 135 +- .../asm_data_aggregator_test.cpp | 37 +- .../asm_dd_aggregator_test.cpp | 10 +- .../listeners/engine_listener_test.cpp | 102 +- appsec/tests/helper/remote_config/mocks.cpp | 40 + appsec/tests/helper/remote_config/mocks.hpp | 27 +- .../helper/remote_config/parser_test.cpp | 1227 --------------- .../helper/remote_config/product_test.cpp | 262 ---- .../helper/remote_config/serializer_test.cpp | 351 ----- appsec/tests/helper/service_manager_test.cpp | 43 +- appsec/tests/helper/service_test.cpp | 78 +- appsec/tests/helper/waf_test.cpp | 36 +- appsec/tests/integration/build.gradle | 1 + .../src/docker/php/Dockerfile-php-deps | 5 +- .../appsec/php/docker/AppSecContainer.groovy | 11 + .../php/mock_agent/ConfigV07Handler.groovy | 44 +- .../php/mock_agent/MockDatadogAgent.groovy | 12 +- .../rem_cfg/RemoteConfigRequest.java | 30 +- .../rem_cfg/RemoteConfigResponse.java | 38 +- .../php/mock_agent/rem_cfg/Target.groovy | 14 + .../src/test/bin/enable_extensions.sh | 1 + .../php/integration/NginxFpmTests.groovy | 2 - .../php/integration/RemoteConfigTests.groovy | 388 +++++ .../src/test/www/base/public/change_env.php | 7 + components-rs/ddtrace.h | 4 +- components-rs/remote_config.rs | 279 +++- ddtrace.sym | 7 + dockerfiles/verify_packages/verify.sh | 2 + ext/ddtrace.c | 6 +- ext/remote_config.c | 7 + ext/remote_config.h | 1 + ext/sidecar.c | 19 +- tests/ext/extension_no_static_tls.phpt | 2 +- .../live-debugger/exception-replay_001.phpt | 2 +- .../live-debugger/exception-replay_002.phpt | 2 +- 127 files changed, 2203 insertions(+), 6436 deletions(-) create mode 100644 appsec/src/helper/remote_config/config.cpp delete mode 100644 appsec/src/helper/remote_config/http_api.cpp delete mode 100644 appsec/src/helper/remote_config/http_api.hpp delete mode 100644 appsec/src/helper/remote_config/product.cpp delete mode 100644 appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/cached_target_files.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/client.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/client_state.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/client_tracer.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/config_state.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/path.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/target_file.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/targets.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/info_response.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/parser.cpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/parser.hpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/serializer.cpp delete mode 100644 appsec/src/helper/remote_config/protocol/tuf/serializer.hpp delete mode 100644 appsec/src/helper/service_identifier.hpp delete mode 100644 appsec/tests/extension/rinit_agent_host_port_01.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_02.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_03.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_04.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_05.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_06.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_07.phpt delete mode 100644 appsec/tests/extension/rinit_agent_host_port_08.phpt delete mode 100644 appsec/tests/helper/remote_config/client_handler_test.cpp delete mode 100644 appsec/tests/helper/remote_config/client_test.cpp create mode 100644 appsec/tests/helper/remote_config/mocks.cpp delete mode 100644 appsec/tests/helper/remote_config/parser_test.cpp delete mode 100644 appsec/tests/helper/remote_config/product_test.cpp delete mode 100644 appsec/tests/helper/remote_config/serializer_test.cpp create mode 100644 appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy create mode 100644 appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy create mode 100644 appsec/tests/integration/src/test/www/base/public/change_env.php diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 2dc12adc07..39ac8aa2fc 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -1411,7 +1411,14 @@ jobs: command: | export DEBIAN_FRONTEND=noninteractive apt update - apt install -y wget sudo git g++ gcc gcovr cmake make curl libcurl4-gnutls-dev clang clang-tidy clang-format git php-dev php8.2-xml php-cgi cargo + apt install -y wget sudo git g++ gcc gcovr cmake make curl libcurl4-gnutls-dev clang clang-tidy clang-format git php-dev php8.2-xml php-cgi + - run: + name: Install rust + command: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh + chmod +x /tmp/rustup.sh + /tmp/rustup.sh -y --default-toolchain 1.76 + sudo ln -s $HOME/.cargo/bin/* /usr/bin/ - run: git config --global --add safe.directory /home/circleci/datadog/appsec/third_party/libddwaf - run: name: CMake @@ -1422,7 +1429,7 @@ jobs: - run: name: Test command: | - make -C appsec/build -j $(nproc) xtest ddappsec_helper_test + PATH=$PATH:$HOME/.cargo/bin make -C appsec/build -j $(nproc) xtest ddappsec_helper_test ./appsec/build/tests/helper/ddappsec_helper_test - run: name: Generate XML coverage @@ -3855,7 +3862,7 @@ jobs: php datadog-setup.php --file "${installable_bundle}" --php-bin php --enable-profiling # run phpize just to get run-tests.php phpize - php run-tests.php -p $(which php) --show-diff -g "FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP" tests/ext/profiling + php run-tests.php -p $(which php) -d datadog.remote_config_enabled=false --show-diff -g "FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP" tests/ext/profiling "cbindgen up-to-date": working_directory: ~/datadog diff --git a/appsec/cmake/helper.cmake b/appsec/cmake/helper.cmake index 7846afb5b6..eb265cdcba 100644 --- a/appsec/cmake/helper.cmake +++ b/appsec/cmake/helper.cmake @@ -20,7 +20,7 @@ set_target_properties(helper_objects PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED YES POSITION_INDEPENDENT_CODE 1) -target_include_directories(helper_objects PUBLIC ${HELPER_INCLUDE_DIR}) +target_include_directories(helper_objects INTERFACE ${HELPER_INCLUDE_DIR}) target_compile_definitions(helper_objects PUBLIC SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) target_compile_options(helper_objects PRIVATE -ftls-model=global-dynamic) target_link_libraries(helper_objects PUBLIC libddwaf_objects pthread spdlog cpp-base64 msgpack_c RapidJSON::rapidjson Boost::system zlibstatic) @@ -35,6 +35,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") # Bind symbols lookup of symbols defined in the library to the library itself # also avoids relocation problems with libc++.a on linux/aarch64 target_link_options(ddappsec-helper PRIVATE -Wl,-Bsymbolic) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_options(ddappsec-helper PRIVATE -undefined dynamic_lookup) endif() set_target_properties(ddappsec-helper PROPERTIES CXX_VISIBILITY_PRESET hidden diff --git a/appsec/src/extension/backtrace.c b/appsec/src/extension/backtrace.c index 0009583c66..8d5e5c8e9f 100644 --- a/appsec/src/extension/backtrace.c +++ b/appsec/src/extension/backtrace.c @@ -48,7 +48,8 @@ php_backtrace_frame_to_datadog_backtrace_frame( // NOLINTNEXTLINE(bugprone-easil if (file) { // In order to be able to test full path encoded everywhere lets set // only the file name without path - char *file_name = memrchr(Z_STRVAL_P(file), '/', Z_STRLEN_P(file)); + const char *file_name = + zend_memrchr(Z_STRVAL_P(file), '/', Z_STRLEN_P(file)); if (file_name) { zend_string *new_file = zend_string_init(file_name + 1, Z_STRLEN_P(file) - (file_name + 1 - Z_STRVAL_P(file)), 0); diff --git a/appsec/src/extension/commands/client_init.c b/appsec/src/extension/commands/client_init.c index 1329561a67..a20513129d 100644 --- a/appsec/src/extension/commands/client_init.c +++ b/appsec/src/extension/commands/client_init.c @@ -17,68 +17,20 @@ #include "../version.h" #include "client_init.h" -static const unsigned int DEFAULT_AGENT_PORT = 8126; -static const char *DEFAULT_AGENT_HOST = "127.0.0.1"; -static const unsigned int MAX_TCP_PORT_ALLOWED = UINT16_MAX; - static dd_result _pack_command(mpack_writer_t *nonnull w, void *nullable ctx); static dd_result _process_response(mpack_node_t root, void *nullable ctx); static void _process_meta_and_metrics( mpack_node_t root, struct req_info *nonnull ctx); -static void _pack_agent_details(mpack_writer_t *nonnull w); static const dd_command_spec _spec = { .name = "client_init", .name_len = sizeof("client_init") - 1, - .num_args = 7, + .num_args = 6, .outgoing_cb = _pack_command, .incoming_cb = _process_response, .config_features_cb = dd_command_process_config_features_unexpected, }; -static void _pack_agent_details(mpack_writer_t *nonnull w) -{ - zend_string *agent_host = get_global_DD_AGENT_HOST(); - zend_string *agent_url = get_global_DD_TRACE_AGENT_URL(); - unsigned int port = get_global_DD_TRACE_AGENT_PORT(); - char *host = NULL; - php_url *parsed_url = NULL; - - if (agent_host && ZSTR_LEN(agent_host) > 0) { - host = ZSTR_VAL(agent_host); - } else if (agent_url && ZSTR_LEN(agent_url) > 0) { - parsed_url = php_url_parse(ZSTR_VAL(agent_url)); - if (parsed_url) { -#if PHP_VERSION_ID < 70300 - if (parsed_url->host && strlen(parsed_url->host) > 0) { - host = parsed_url->host; - } -#else - if (parsed_url->host && ZSTR_LEN(parsed_url->host) > 0) { - host = ZSTR_VAL(parsed_url->host); - } -#endif - port = parsed_url->port; - } - } - - if (!host) { - host = (char *)DEFAULT_AGENT_HOST; - } - if (port <= 0 || port > MAX_TCP_PORT_ALLOWED) { - port = DEFAULT_AGENT_PORT; - } - - dd_mpack_write_lstr(w, "host"); - dd_mpack_write_nullable_cstr(w, host); - dd_mpack_write_lstr(w, "port"); - mpack_write_uint(w, port); - - if (parsed_url) { - php_url_free(parsed_url); - } -} - dd_result dd_client_init(dd_conn *nonnull conn, struct req_info *nonnull ctx) { return dd_command_exec_cred(conn, &_spec, ctx); @@ -97,39 +49,6 @@ static dd_result _pack_command( mpack_write_bool(w, DDAPPSEC_G(active)); } - // Service details - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - mpack_start_map(w, 6); - - dd_mpack_write_lstr(w, "service"); - dd_mpack_write_nullable_cstr(w, ZSTR_VAL(get_DD_SERVICE())); - - dd_mpack_write_lstr(w, "extra_services"); - zval extra_services; - ZVAL_ARR(&extra_services, get_global_DD_EXTRA_SERVICES()); - dd_mpack_write_zval(w, &extra_services); - - dd_mpack_write_lstr(w, "env"); - dd_mpack_write_nullable_cstr(w, ZSTR_VAL(get_DD_ENV())); - - dd_mpack_write_lstr(w, "tracer_version"); - dd_mpack_write_nullable_cstr(w, dd_trace_version()); - - dd_mpack_write_lstr(w, "app_version"); - dd_mpack_write_nullable_cstr(w, ZSTR_VAL(get_DD_VERSION())); - - // We send this empty for now. The helper will check for empty and if so it - // will generate it - dd_mpack_write_lstr(w, "runtime_id"); - zend_string *runtime_id = dd_trace_get_formatted_runtime_id(false); - if (runtime_id == NULL) { - dd_mpack_write_nullable_cstr(w, ""); - } else { - dd_mpack_write_nullable_zstr(w, runtime_id); - zend_string_free(runtime_id); - } - mpack_finish_map(w); - // Engine settings // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) mpack_start_map(w, 6); @@ -180,15 +99,13 @@ static dd_result _pack_command( // Remote config settings // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - mpack_start_map(w, 4); + mpack_start_map(w, 2); dd_mpack_write_lstr(w, "enabled"); mpack_write_bool(w, get_DD_REMOTE_CONFIG_ENABLED()); - _pack_agent_details(w); - - dd_mpack_write_lstr(w, "poll_interval"); - mpack_write_u32(w, get_DD_REMOTE_CONFIG_POLL_INTERVAL()); + dd_mpack_write_lstr(w, "shmem_path"); + dd_mpack_write_nullable_cstr(w, dd_trace_remote_config_get_path()); mpack_finish_map(w); diff --git a/appsec/src/extension/commands/config_sync.c b/appsec/src/extension/commands/config_sync.c index 1938561d5e..c7fcbddd58 100644 --- a/appsec/src/extension/commands/config_sync.c +++ b/appsec/src/extension/commands/config_sync.c @@ -8,32 +8,36 @@ #include #include "../commands_helpers.h" +#include "../ddtrace.h" +#include "../msgpack_helpers.h" +#include "config_sync.h" #include -static dd_result _request_pack( - mpack_writer_t *nonnull w, void *nullable ATTR_UNUSED ctx); +static dd_result _request_pack(mpack_writer_t *nonnull w, void *nonnull ctx); dd_result dd_command_process_config_sync( mpack_node_t root, ATTR_UNUSED void *unspecnull ctx); static const dd_command_spec _spec = { .name = "config_sync", .name_len = sizeof("config_sync") - 1, - .num_args = 0, // a single map + .num_args = 1, .outgoing_cb = _request_pack, .incoming_cb = dd_command_process_config_sync, .config_features_cb = dd_command_process_config_features, }; -dd_result dd_config_sync(dd_conn *nonnull conn) +dd_result dd_config_sync( + dd_conn *nonnull conn, const struct config_sync_data *nonnull data) { - return dd_command_exec(conn, &_spec, NULL); + return dd_command_exec(conn, &_spec, (void *)data); } -static dd_result _request_pack( - mpack_writer_t *nonnull w, void *nullable ATTR_UNUSED ctx) +static dd_result _request_pack(mpack_writer_t *nonnull w, void *nonnull ctx_) { - UNUSED(ctx); - UNUSED(w); + const struct config_sync_data *nonnull data = + (struct config_sync_data *)ctx_; + + dd_mpack_write_nullable_cstr(w, data->rem_cfg_path); return dd_success; } diff --git a/appsec/src/extension/commands/config_sync.h b/appsec/src/extension/commands/config_sync.h index 95a6ae39a5..eeb8976485 100644 --- a/appsec/src/extension/commands/config_sync.h +++ b/appsec/src/extension/commands/config_sync.h @@ -7,4 +7,9 @@ #include "../network.h" -dd_result dd_config_sync(dd_conn *nonnull conn); +struct config_sync_data { + char *nullable rem_cfg_path; +}; + +dd_result dd_config_sync( + dd_conn *nonnull conn, const struct config_sync_data *nonnull data); diff --git a/appsec/src/extension/ddappsec.c b/appsec/src/extension/ddappsec.c index 26fbaa6b2b..f7a9567052 100644 --- a/appsec/src/extension/ddappsec.c +++ b/appsec/src/extension/ddappsec.c @@ -45,6 +45,7 @@ #include "user_tracking.h" #include +#include #if ZTS static atomic_int _thread_count; @@ -100,7 +101,7 @@ static zend_extension ddappsec_extension_entry = { PHP_DDAPPSEC_EXTNAME, PHP_DDAPPSEC_VERSION, "Datadog", - "https://github.com/DataDog/dd-appsec-php", + "https://github.com/DataDog/dd-trace-php", "Copyright Datadog", ddappsec_startup, NULL, @@ -253,6 +254,21 @@ void dd_appsec_rinit_once() pthread_once(&_rinit_once_control, _rinit_once); } +static void _warn_on_empty_service_or_env() +{ + if (!get_global_DD_APPSEC_TESTING() && get_DD_REMOTE_CONFIG_ENABLED() && + DDAPPSEC_G(enabled) != APPSEC_FULLY_DISABLED && + (zend_string_equals_literal(get_DD_ENV(), "") || + zend_string_equals_literal(get_DD_SERVICE(), ""))) { + mlog(dd_log_warning, + "AppSec is not disabled and Datadog service or env is empty. " + "Please set DD_SERVICE and DD_ENV rather than setting the " + "corresponding properties on the root span. Otherwise, remote " + "configuration for AppSec will use service=unnamed-php-service and " + "env=none"); + } +} + // NOLINTNEXTLINE static PHP_RINIT_FUNCTION(ddappsec) { @@ -265,6 +281,7 @@ static PHP_RINIT_FUNCTION(ddappsec) dd_appsec_rinit_once(); zai_config_rinit(); _check_enabled(); + _warn_on_empty_service_or_env(); if (DDAPPSEC_G(enabled) == APPSEC_FULLY_DISABLED) { return SUCCESS; @@ -378,6 +395,22 @@ static void _check_enabled() }; } +__attribute__((visibility("default"))) void dd_appsec_rc_conf( + bool *nonnull appsec_features, bool *nonnull appsec_conf) // NOLINT +{ + bool prev_enabled = DDAPPSEC_G(enabled); + bool prev_active = DDAPPSEC_G(active); + bool prev_to_be_configured = DDAPPSEC_G(to_be_configured); + _check_enabled(); + DDAPPSEC_G(enabled) = prev_enabled; + DDAPPSEC_G(active) = prev_active; + DDAPPSEC_G(to_be_configured) = prev_to_be_configured; + + *appsec_features = DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG; + // only enable ASM / ASM_DD / ASM_DATA if no rules file is specified + *appsec_conf = get_global_DD_APPSEC_RULES()->len == 0; +} + static PHP_FUNCTION(datadog_appsec_is_enabled) { if (zend_parse_parameters_none() == FAILURE) { diff --git a/appsec/src/extension/ddappsec.h b/appsec/src/extension/ddappsec.h index e019a2850b..7983ca2d4b 100644 --- a/appsec/src/extension/ddappsec.h +++ b/appsec/src/extension/ddappsec.h @@ -56,6 +56,9 @@ extern __thread void *unspecnull ATTR_TLS_LOCAL_DYNAMIC TSRMLS_CACHE; void dd_appsec_rinit_once(void); int dd_appsec_rshutdown(bool ignore_verdict); +__attribute__((visibility("default"))) void dd_appsec_rc_conf( + bool *nonnull appsec_features, bool *nonnull appsec_conf); // NOLINT + // Add a NO_CACHE version. // Use tsrm_get_ls_cache() instead of thread-local _tsrmls_ls_cache #ifdef ZTS diff --git a/appsec/src/extension/ddappsec.version b/appsec/src/extension/ddappsec.version index eb4dab81b2..7ab9abb2c3 100644 --- a/appsec/src/extension/ddappsec.version +++ b/appsec/src/extension/ddappsec.version @@ -2,5 +2,6 @@ global: get_module; dd_appsec_maybe_enable_helper; + dd_appsec_rc_conf; local: *; }; diff --git a/appsec/src/extension/ddtrace.c b/appsec/src/extension/ddtrace.c index d9dd88c052..36fb0da7fd 100644 --- a/appsec/src/extension/ddtrace.c +++ b/appsec/src/extension/ddtrace.c @@ -45,6 +45,8 @@ static bool (*nullable _ddtrace_user_req_add_listeners)( static zend_string *(*_ddtrace_ip_extraction_find)(zval *server); +static const char *nullable (*_ddtrace_remote_config_get_path)(void); + static void dd_trace_load_symbols(void) { bool testing = get_global_DD_APPSEC_TESTING(); @@ -98,6 +100,14 @@ static void dd_trace_load_symbols(void) dlerror()); // NOLINT(concurrency-mt-unsafe) } + _ddtrace_remote_config_get_path = + dlsym(handle, "ddtrace_remote_config_get_path"); + if (_ddtrace_remote_config_get_path == NULL && !testing) { + mlog(dd_log_error, + // NOLINTNEXTLINE(concurrency-mt-unsafe) + "Failed to load ddtrace_remote_config_get_path: %s", dlerror()); + } + dlclose(handle); } @@ -358,6 +368,16 @@ zend_string *nullable dd_ip_extraction_find(zval *nonnull server) return _ddtrace_ip_extraction_find(server); } +const char *nullable dd_trace_remote_config_get_path() +{ + if (!_ddtrace_remote_config_get_path) { + return NULL; + } + __auto_type path = _ddtrace_remote_config_get_path(); + mlog(dd_log_trace, "Remote config path: %s", path ? path : "(unset)"); + return path; +} + static PHP_FUNCTION(datadog_appsec_testing_ddtrace_rshutdown) { if (zend_parse_parameters_none() == FAILURE) { diff --git a/appsec/src/extension/ddtrace.h b/appsec/src/extension/ddtrace.h index 9e634b6fec..4a8a6ca624 100644 --- a/appsec/src/extension/ddtrace.h +++ b/appsec/src/extension/ddtrace.h @@ -79,3 +79,5 @@ bool dd_trace_user_req_add_listeners( ddtrace_user_req_listeners *nonnull listeners); zend_string *nullable dd_ip_extraction_find(zval *nonnull server); + +const char *nullable dd_trace_remote_config_get_path(void); diff --git a/appsec/src/extension/helper_process.c b/appsec/src/extension/helper_process.c index 06f7a76443..8b15e64a2e 100644 --- a/appsec/src/extension/helper_process.c +++ b/appsec/src/extension/helper_process.c @@ -130,7 +130,8 @@ dd_conn *nullable dd_helper_mgr_cur_conn(void) } // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -bool dd_on_runtime_path_update(zval *nullable old_val, zval *nonnull new_val, zend_string *nonnull new_str) +bool dd_on_runtime_path_update(zval *nullable old_val, zval *nonnull new_val, + zend_string *nullable new_str) { UNUSED(old_val); UNUSED(new_str); diff --git a/appsec/src/extension/helper_process.h b/appsec/src/extension/helper_process.h index ebf6b989eb..9534bfdecf 100644 --- a/appsec/src/extension/helper_process.h +++ b/appsec/src/extension/helper_process.h @@ -23,11 +23,12 @@ void dd_helper_gshutdown(void); void dd_helper_rshutdown(void); typedef dd_result (*client_init_func)(dd_conn *nonnull, void *unspecnull ctx); -dd_conn *nullable dd_helper_mgr_acquire_conn(client_init_func nonnull, void *unspecnull ctx); +dd_conn *nullable dd_helper_mgr_acquire_conn( + client_init_func nonnull, void *unspecnull ctx); dd_conn *nullable dd_helper_mgr_cur_conn(void); void dd_helper_close_conn(void); -bool dd_on_runtime_path_update( - zval *nullable old_value, zval *nonnull new_value, zend_string *nonnull new_str); +bool dd_on_runtime_path_update(zval *nullable old_value, + zval *nonnull new_value, zend_string *nullable new_str); #endif // DD_HELPER_MGR_H diff --git a/appsec/src/extension/msgpack_helpers.c b/appsec/src/extension/msgpack_helpers.c index 535690cff1..69d90c60f2 100644 --- a/appsec/src/extension/msgpack_helpers.c +++ b/appsec/src/extension/msgpack_helpers.c @@ -292,9 +292,10 @@ static bool parse_element( case mpack_type_int: ZVAL_LONG(output, mpack_tag_int_value(&tag)); break; - case mpack_type_uint: - ZVAL_LONG(output, mpack_tag_int_value(&tag)); + case mpack_type_uint: { + ZVAL_LONG(output, (long)mpack_tag_uint_value(&tag)); break; + } case mpack_type_str: { uint32_t length = mpack_tag_str_length(&tag); diff --git a/appsec/src/extension/request_lifecycle.c b/appsec/src/extension/request_lifecycle.c index 0ed9d1d7d8..3aa826d903 100644 --- a/appsec/src/extension/request_lifecycle.c +++ b/appsec/src/extension/request_lifecycle.c @@ -47,6 +47,9 @@ static THREAD_LOCAL_ON_ZTS zend_array *nullable _superglob_equiv; static THREAD_LOCAL_ON_ZTS zend_string *nullable _client_ip; static THREAD_LOCAL_ON_ZTS zval _blocking_function; static THREAD_LOCAL_ON_ZTS bool _shutdown_done_on_commit; +#define MAX_LENGTH_OF_REM_CFG_PATH 31 +static THREAD_LOCAL_ON_ZTS char + _last_rem_cfg_path[MAX_LENGTH_OF_REM_CFG_PATH + 1]; #define CLIENT_IP_LOOKUP_FAILED ((zend_string *)-1) bool dd_req_is_user_req() { return _enabled_user_req; } @@ -125,6 +128,31 @@ static zend_array *nullable _do_request_begin_user_req(zval *nullable rbe_zv) return _do_request_begin(rbe_zv, true); } +static bool _rem_cfg_path_changed() +{ + const char *cur_path = dd_trace_remote_config_get_path(); + if (!cur_path) { + cur_path = ""; + } + if (strcmp(cur_path, _last_rem_cfg_path) == 0) { + return false; + } + + if (strlen(cur_path) > MAX_LENGTH_OF_REM_CFG_PATH) { + mlog(dd_log_warning, "Remote config path too long: %s", cur_path); + return false; + } + + mlog(dd_log_info, "Remote config path changed from %s to %s", + _last_rem_cfg_path[0] ? _last_rem_cfg_path : "(none)", + cur_path[0] ? cur_path : "(none)"); + + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy) + strcpy(_last_rem_cfg_path, cur_path); + + return true; +} + static zend_array *nullable _do_request_begin( zval *nullable rbe_zv /* needs free */, bool user_req) { @@ -156,16 +184,17 @@ static zend_array *nullable _do_request_begin( } int res = dd_success; - if (DDAPPSEC_G(active)) { - // request_init - res = dd_request_init(conn, &req_info); - } else if (DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG) { - // config_sync - res = dd_config_sync(conn); - if (res == SUCCESS && DDAPPSEC_G(active)) { - // Since it came as enabled, lets proceed + if (_rem_cfg_path_changed() || + (!DDAPPSEC_G(active) && + DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG)) { + res = dd_config_sync(conn, + &(struct config_sync_data){.rem_cfg_path = _last_rem_cfg_path}); + if (res == dd_success && DDAPPSEC_G(active)) { res = dd_request_init(conn, &req_info); } + } else if (DDAPPSEC_G(active)) { + // request_init + res = dd_request_init(conn, &req_info); } if (rbe) { diff --git a/appsec/src/helper/client.cpp b/appsec/src/helper/client.cpp index 078dffd224..17b9470176 100644 --- a/appsec/src/helper/client.cpp +++ b/appsec/src/helper/client.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include +#include #include #include #include @@ -146,12 +147,11 @@ bool handle_message(client &client, const network::base_broker &broker, bool client::handle_command(const network::client_init::request &command) { SPDLOG_DEBUG("Got client_id with pid={}, client_version={}, " - "runtime_version={}, service={}, engine_settings={}, " + "runtime_version={}, engine_settings={}, " "remote_config_settings={}", command.pid, command.client_version, command.runtime_version, - command.service, command.engine_settings, command.rc_settings); + command.engine_settings, command.rc_settings); - auto service_id = command.service; auto &&eng_settings = command.engine_settings; DD_STDLOG(DD_STDLOG_STARTUP); @@ -162,19 +162,15 @@ bool client::handle_command(const network::client_init::request &command) bool has_errors = false; client_enabled_conf = command.enabled_configuration; - if (service_id.runtime_id.empty()) { - service_id.runtime_id = generate_random_uuid(); - } - runtime_id_ = service_id.runtime_id; try { - service_ = service_manager_->create_service(std::move(service_id), - eng_settings, command.rc_settings, meta, metrics, - !client_enabled_conf.has_value()); - if (service_) { - // This null check is only needed due to some tests - service_->register_runtime_id(runtime_id_); - } + service_ = + service_manager_->create_service(eng_settings, command.rc_settings, + meta, metrics, !client_enabled_conf.has_value()); + + // save engine settings so we can recreate the service if rc path + // changes + engine_settings_ = eng_settings; } catch (std::system_error &e) { // TODO: logging should happen at WAF impl DD_STDLOG(DD_STDLOG_RULES_FILE_NOT_FOUND, @@ -340,13 +336,22 @@ bool client::compute_client_status() return request_enabled_; } -bool client::handle_command(network::config_sync::request & /* command */) +bool client::handle_command(network::config_sync::request &command) { if (!service_guard()) { return false; } - SPDLOG_DEBUG("received command config_sync"); + SPDLOG_DEBUG( + "received command config_sync with path {}", command.rem_cfg_path); + + std::map meta; + std::map metrics; + update_remote_config_path(command.rem_cfg_path, meta, metrics); + + // TODO: meta/metrics not transmitted fwd + // wait for new interface; see + // https://github.com/DataDog/dd-trace-php/pull/2735 if (compute_client_status()) { auto response_cf = @@ -363,7 +368,7 @@ bool client::handle_command(network::config_sync::request & /* command */) return true; } - SPDLOG_DEBUG("sending config_sync to config_sync"); + SPDLOG_DEBUG("sending response to config_sync"); try { return broker_->send( std::make_shared()); @@ -434,6 +439,24 @@ bool client::handle_command(network::request_shutdown::request &command) return send_message(response); } +void client::update_remote_config_path(std::string_view path, + std::map &meta, + std::map &metrics) +{ + if (service_->is_remote_config_shmem_path(path) || + !engine_settings_.has_value()) { + return; + } + + SPDLOG_INFO("Remote config path changed to {}, recreating service", path); + remote_config::settings rc_settings; + rc_settings.enabled = true; + rc_settings.shmem_path = path; + + service_ = service_manager_->create_service( + *engine_settings_, rc_settings, meta, metrics, true); +} + bool client::run_client_init() { static constexpr auto client_init_timeout{std::chrono::milliseconds{500}}; @@ -457,12 +480,6 @@ bool client::run_request() void client::run(worker::queue_consumer &q) { - const defer on_exit{[this]() { - if (this->service_) { - this->service_->unregister_runtime_id(this->runtime_id_); - } - }}; - if (q.running()) { if (!run_client_init()) { SPDLOG_DEBUG("Finished handling client (client_init failed)"); diff --git a/appsec/src/helper/client.hpp b/appsec/src/helper/client.hpp index 81722deb63..aa5f8a15b7 100644 --- a/appsec/src/helper/client.hpp +++ b/appsec/src/helper/client.hpp @@ -61,6 +61,10 @@ class client { void run(worker::queue_consumer &q); bool compute_client_status(); + void update_remote_config_path(std::string_view path, + std::map &meta, + std::map &metrics); + protected: template std::shared_ptr publish(typename T::request &command); @@ -71,11 +75,11 @@ class client { uint32_t version{}; network::base_broker::ptr broker_; std::shared_ptr service_manager_; + std::optional engine_settings_; std::shared_ptr service_ = {nullptr}; std::optional context_; std::optional client_enabled_conf; bool request_enabled_ = {false}; - std::string runtime_id_; }; } // namespace dds diff --git a/appsec/src/helper/engine.cpp b/appsec/src/helper/engine.cpp index 52d5f7c1b7..e9b6fcabfb 100644 --- a/appsec/src/helper/engine.cpp +++ b/appsec/src/helper/engine.cpp @@ -4,9 +4,7 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include -#include -#include -#include +#include #include #include "engine.hpp" @@ -19,17 +17,16 @@ namespace dds { -void engine::subscribe(const subscriber::ptr &sub) +void engine::subscribe(std::unique_ptr sub) { - auto common = std::atomic_load(&common_); - common->subscribers.emplace_back(sub); + common_->subscribers.emplace_back(std::move(sub)); } void engine::update(engine_ruleset &ruleset, std::map &meta, std::map &metrics) { - std::vector new_subscribers; + std::vector> new_subscribers; new_subscribers.reserve(common_->subscribers.size()); dds::parameter param = json_to_parameter(ruleset.get_document()); for (auto &sub : common_->subscribers) { @@ -38,18 +35,17 @@ void engine::update(engine_ruleset &ruleset, } catch (const std::exception &e) { SPDLOG_WARN("Failed to update subscriber {}: {}", sub->get_name(), e.what()); - new_subscribers.emplace_back(sub); + return; // no partial updates } catch (...) { SPDLOG_WARN("Failed to update subscriber {}: unknown reason", sub->get_name()); - new_subscribers.emplace_back(sub); + return; } } - std::shared_ptr const new_common( - new shared_state{std::move(new_subscribers)}); - - std::atomic_store(&common_, new_common); + auto new_common = std::make_shared( + shared_state{std::move(new_subscribers)}); + std::atomic_store_explicit(&common_, new_common, std::memory_order_release); } std::optional engine::context::publish(parameter &¶m) @@ -70,12 +66,18 @@ std::optional engine::context::publish(parameter &¶m) event event_; for (auto &sub : common_->subscribers) { - auto it = listeners_.find(sub); + auto it = listeners_.find(sub.get()); if (it == listeners_.end()) { - it = listeners_.emplace(sub, sub->get_listener()).first; + auto listener = sub->get_listener(); + assert(listener.get() != nullptr); + auto &&[iterator, inserted] = + listeners_.emplace(sub.get(), std::move(listener)); + assert(inserted == true); + it = iterator; } try { - it->second->call(data, event_); + const auto &listener = it->second; + listener->call(data, event_); } catch (std::exception &e) { SPDLOG_ERROR("subscriber failed: {}", e.what()); } @@ -116,21 +118,22 @@ void engine::context::get_meta_and_metrics( } } -engine::ptr engine::from_settings(const dds::engine_settings &eng_settings, +std::unique_ptr engine::from_settings( + const dds::engine_settings &eng_settings, std::map &meta, std::map &metrics) - { auto &&rules_path = eng_settings.rules_file_or_default(); auto ruleset = engine_ruleset::from_path(rules_path); - std::shared_ptr engine_ptr{engine::create(eng_settings.trace_rate_limit)}; + std::unique_ptr engine_ptr{ + engine::create(eng_settings.trace_rate_limit)}; try { SPDLOG_DEBUG("Will load WAF rules from {}", rules_path); // may throw std::exception - const subscriber::ptr waf = + auto waf = waf::instance::from_settings(eng_settings, ruleset, meta, metrics); - engine_ptr->subscribe(waf); + engine_ptr->subscribe(std::move(waf)); } catch (...) { DD_STDLOG(DD_STDLOG_WAF_INIT_FAILED, rules_path); throw; diff --git a/appsec/src/helper/engine.hpp b/appsec/src/helper/engine.hpp index b42600a59b..a42797e8db 100644 --- a/appsec/src/helper/engine.hpp +++ b/appsec/src/helper/engine.hpp @@ -12,6 +12,7 @@ #include "parameter.hpp" #include "rate_limit.hpp" #include "subscriber/base.hpp" +#include #include #include #include @@ -34,10 +35,6 @@ namespace dds { **/ class engine { public: - using ptr = std::shared_ptr; - using subscription_map = - std::map>; - using action_map = std::unordered_map; struct result { @@ -48,7 +45,7 @@ class engine { protected: struct shared_state { - std::vector subscribers; + std::vector> subscribers; }; public: @@ -58,8 +55,9 @@ class engine { class context { public: explicit context(engine &engine) - : common_(std::atomic_load(&engine.common_)), - limiter_(engine.limiter_) + : common_{std::atomic_load_explicit( + &engine.common_, std::memory_order_acquire)}, + limiter_{engine.limiter_} {} context(const context &) = delete; context &operator=(const context &) = delete; @@ -73,10 +71,12 @@ class engine { std::map &metrics); protected: - std::vector prev_published_params_; - std::map listeners_; std::shared_ptr common_; - rate_limiter &limiter_; + std::map> + listeners_; + std::vector prev_published_params_; + rate_limiter & + limiter_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; engine(const engine &) = delete; @@ -85,30 +85,32 @@ class engine { engine &operator=(engine &&) = delete; virtual ~engine() = default; - static engine::ptr from_settings(const dds::engine_settings &eng_settings, + static std::unique_ptr from_settings( + const dds::engine_settings &eng_settings, std::map &meta, std::map &metrics); static auto create( uint32_t trace_rate_limit = engine_settings::default_trace_rate_limit) { - return std::shared_ptr(new engine(trace_rate_limit)); + return std::unique_ptr(new engine(trace_rate_limit)); } context get_context() { return context{*this}; } - void subscribe(const subscriber::ptr &sub); - // Update is not thread-safe, although only one remote config client should - // be able to update it so in practice it should not be a problem. + // Not thread-safe, should only be called after construction + void subscribe(std::unique_ptr sub); + virtual void update(engine_ruleset &ruleset, std::map &meta, std::map &metrics); protected: - explicit engine(uint32_t trace_rate_limit, action_map &&actions = {}) + explicit engine(uint32_t trace_rate_limit) : limiter_(trace_rate_limit), common_(new shared_state{{}}) {} + // in practice: the current ddwaf_handle, atomically swapped in update std::shared_ptr common_; rate_limiter limiter_; }; diff --git a/appsec/src/helper/engine_settings.hpp b/appsec/src/helper/engine_settings.hpp index e9bdf91012..b61fc52b41 100644 --- a/appsec/src/helper/engine_settings.hpp +++ b/appsec/src/helper/engine_settings.hpp @@ -39,6 +39,11 @@ struct engine_settings { std::string obfuscator_value_regex; schema_extraction_settings schema_extraction; + engine_settings() = default; + engine_settings(const engine_settings &) = default; + engine_settings(engine_settings &&) = default; + engine_settings &operator=(const engine_settings &) = default; + engine_settings &operator=(engine_settings &&) = default; virtual ~engine_settings() = default; static const std::string &default_rules_file(); @@ -79,14 +84,16 @@ struct engine_settings { << ", schema_extraction.sample_rate=" << std::fixed << c.schema_extraction.sample_rate << "}"; } - - struct settings_hash { - std::size_t operator()(const engine_settings &s) const noexcept - { - return hash(s.rules_file, s.waf_timeout_us, s.trace_rate_limit, - s.obfuscator_key_regex, s.obfuscator_value_regex, - s.schema_extraction.enabled, s.schema_extraction.sample_rate); - } - }; }; } // namespace dds + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::engine_settings &s) const noexcept + { + return dds::hash(s.rules_file, s.waf_timeout_us, s.trace_rate_limit, + s.obfuscator_key_regex, s.obfuscator_value_regex, + s.schema_extraction.enabled, s.schema_extraction.sample_rate); + } +}; +} // namespace std diff --git a/appsec/src/helper/json_helper.cpp b/appsec/src/helper/json_helper.cpp index 45d0c54f8c..4712ddfc15 100644 --- a/appsec/src/helper/json_helper.cpp +++ b/appsec/src/helper/json_helper.cpp @@ -200,19 +200,10 @@ json_helper::get_field_of_type( return get_field_of_type(*parent_field, key, type); } -bool json_helper::get_json_base64_encoded_content( +bool json_helper::parse_json( const std::string &content, rapidjson::Document &output) { - std::string base64_decoded; - try { - base64_decoded = base64_decode(content, true); - } catch (const std::runtime_error &error) { - SPDLOG_DEBUG( - "Invalid base64 encoded content: " + std::string(error.what())); - return false; - } - - if (output.Parse(base64_decoded).HasParseError()) { + if (output.Parse(content).HasParseError()) { SPDLOG_DEBUG("Invalid json: " + std::string(rapidjson::GetParseError_En( output.GetParseError()))); return false; diff --git a/appsec/src/helper/json_helper.hpp b/appsec/src/helper/json_helper.hpp index 8124628cbf..52b4db8901 100644 --- a/appsec/src/helper/json_helper.hpp +++ b/appsec/src/helper/json_helper.hpp @@ -63,8 +63,7 @@ std::optional get_field_of_type( std::optional get_field_of_type( rapidjson::Value::ConstValueIterator parent_field, std::string_view key, rapidjson::Type type); -bool get_json_base64_encoded_content( - const std::string &content, rapidjson::Document &output); +bool parse_json(const std::string &content, rapidjson::Document &output); // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) void merge_arrays(rapidjson::Value &destination, rapidjson::Value &source, rapidjson::Value::AllocatorType &allocator); diff --git a/appsec/src/helper/main.cpp b/appsec/src/helper/main.cpp index ec4155704d..c76d880910 100644 --- a/appsec/src/helper/main.cpp +++ b/appsec/src/helper/main.cpp @@ -1,5 +1,5 @@ -// Unless explicitly stated otherwise all files in this repository are // dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// Unless explicitly stated otherwise all files in this repository are // // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. @@ -9,7 +9,6 @@ #include "config.hpp" #include "runner.hpp" -#include "subscriber/waf.hpp" #include #include #include @@ -111,7 +110,10 @@ int appsec_helper_main_impl() return 1; } - auto runner = std::make_unique(config, interrupted); + dds::remote_config::resolve_symbols(); + dds::runner::resolve_symbols(); + + auto runner = std::make_shared(config, interrupted); SPDLOG_INFO("starting runner on new thread"); std::thread thr{[runner = std::move(runner)]() { #ifdef __linux__ @@ -119,6 +121,8 @@ int appsec_helper_main_impl() #elif defined(__APPLE__) pthread_setname_np("appsec_helper runner"); #endif + runner->register_for_rc_notifications(); + runner->run(); finished.store(true, std::memory_order_release); diff --git a/appsec/src/helper/network/proto.hpp b/appsec/src/helper/network/proto.hpp index 819d321cd2..3c629b4363 100644 --- a/appsec/src/helper/network/proto.hpp +++ b/appsec/src/helper/network/proto.hpp @@ -5,15 +5,14 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "engine_settings.hpp" +#include "../engine_settings.hpp" +#include "../remote_config/settings.hpp" +#include "../version.hpp" #include "msgpack_helpers.hpp" -#include "remote_config/settings.hpp" -#include "service_identifier.hpp" #include #include #include #include -#include using stream_packer = msgpack::packer; @@ -101,7 +100,6 @@ struct client_init { std::string runtime_version; std::optional enabled_configuration; - dds::service_identifier service; dds::engine_settings engine_settings; dds::remote_config::settings rc_settings; @@ -113,7 +111,7 @@ struct client_init { ~request() override = default; MSGPACK_DEFINE(pid, client_version, runtime_version, - enabled_configuration, service, engine_settings, rc_settings); + enabled_configuration, engine_settings, rc_settings); }; struct response : base_response_generic { @@ -223,7 +221,10 @@ struct config_sync { request(request &&) = default; request &operator=(request &&) = default; ~request() override = default; - MSGPACK_DEFINE() + + std::string rem_cfg_path; + + MSGPACK_DEFINE(rem_cfg_path) }; struct response : base_response_generic { diff --git a/appsec/src/helper/rate_limit.hpp b/appsec/src/helper/rate_limit.hpp index c9ac7671b6..7797582852 100644 --- a/appsec/src/helper/rate_limit.hpp +++ b/appsec/src/helper/rate_limit.hpp @@ -12,10 +12,6 @@ #include #include "timer.hpp" -using std::chrono::duration_cast; -using std::chrono::microseconds; -using std::chrono::milliseconds; -using std::chrono::seconds; namespace dds { @@ -25,6 +21,11 @@ template class rate_limiter { : max_per_second_(max_per_second){}; bool allow() { + using std::chrono::duration_cast; + using std::chrono::microseconds; + using std::chrono::milliseconds; + using std::chrono::seconds; + if (max_per_second_ == 0) { return true; } diff --git a/appsec/src/helper/remote_config/client.cpp b/appsec/src/helper/remote_config/client.cpp index 26ab48c948..32d96c5c09 100644 --- a/appsec/src/helper/remote_config/client.cpp +++ b/appsec/src/helper/remote_config/client.cpp @@ -6,240 +6,186 @@ #include "client.hpp" #include "exception.hpp" #include "product.hpp" -#include "protocol/tuf/parser.hpp" -#include "protocol/tuf/serializer.hpp" #include #include +#include #include -#include +#include +#include + +extern "C" { +#include +} + +namespace { +struct ddog_CharSlice { + const char *ptr; + uintptr_t len; +}; +ddog_RemoteConfigReader *(*ddog_remote_config_reader_for_path)( + const char *path); +bool (*ddog_remote_config_read)( + ddog_RemoteConfigReader *reader, ddog_CharSlice *data); +void (*ddog_remote_config_reader_drop)(struct ddog_RemoteConfigReader *); +} // namespace namespace dds::remote_config { -config_path config_path::from_path(const std::string &path) +void resolve_symbols() { - static const std::regex regex( - "^(datadog/\\d+|employee)/([^/]+)/([^/]+)/[^/]+$"); + ddog_remote_config_reader_for_path = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_reader_for_path")); + if (ddog_remote_config_reader_for_path == nullptr) { + throw std::runtime_error{ + "Failed to resolve ddog_remote_config_reader_for_path"}; + } - std::smatch base_match; - if (!std::regex_match(path, base_match, regex) || base_match.size() < 4) { - throw invalid_path(); + ddog_remote_config_read = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_read")); + if (ddog_remote_config_read == nullptr) { + throw std::runtime_error{"Failed to resolve ddog_remote_config_read"}; } - return config_path{base_match[3].str(), base_match[2].str()}; + ddog_remote_config_reader_drop = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_reader_drop")); + if (ddog_remote_config_reader_drop == nullptr) { + throw std::runtime_error{ + "Failed to resolve ddog_remote_config_reader_drop"}; + } } -client::client(std::unique_ptr &&arg_api, service_identifier &&sid, - remote_config::settings settings, - std::vector listeners) - : api_(std::move(arg_api)), id_(dds::generate_random_uuid()), - sid_(std::move(sid)), settings_(std::move(settings)), - listeners_(std::move(listeners)) +client::client(remote_config::settings settings, + std::vector> listeners) + : reader_{ddog_remote_config_reader_for_path(settings.shmem_path.c_str()), + ddog_remote_config_reader_drop}, + settings_{std::move(settings)}, listeners_{std::move(listeners)} { + assert(settings.enabled == true); // NOLINT + for (auto const &listener : listeners_) { - const auto &supported_products = listener->get_supported_products(); - for (const auto &[name, capabilities] : supported_products) { - products_.insert(std::pair( - name, product(name, listener))); - capabilities_ |= capabilities; + for (const product p : listener->get_supported_products()) { + std::vector &vec_listeners = + listeners_per_product_[p]; + vec_listeners.push_back(listener.get()); } } } -client::ptr client::from_settings(service_identifier &&sid, +std::unique_ptr client::from_settings( const remote_config::settings &settings, - std::vector listeners) + std::vector> listeners) { - return std::make_unique(std::make_unique(settings.host, - std::to_string(settings.port)), - std::move(sid), settings, std::move(listeners)); + return std::unique_ptr{new client(settings, std::move(listeners))}; } -[[nodiscard]] protocol::get_configs_request client::generate_request() const +bool client::poll() { - std::vector config_states; - std::vector files; - - for (const auto &[product_name, product] : products_) { - // State - const auto configs_on_product = product.get_configs(); - for (const auto &[id, config] : configs_on_product) { - config_states.push_back({config.id, config.version, config.product, - config.apply_state, config.apply_error}); - - std::vector hashes; - hashes.reserve(config.hashes.size()); - for (auto const &[algo, hash_sting] : config.hashes) { - hashes.push_back({algo, hash_sting}); - } - files.push_back({config.path, config.length, std::move(hashes)}); - } + SPDLOG_DEBUG("Polling remote config"); + + ddog_CharSlice slice{}; + const bool has_update = ddog_remote_config_read(reader_.get(), &slice); + if (!has_update) { + SPDLOG_DEBUG("No update available for {}", settings_.shmem_path); + return false; } - const protocol::client_tracer ct{std::move(ids_.get()), sid_.tracer_version, - sid_.service, sid_.extra_services, sid_.env, sid_.app_version}; + std::set new_configs; + // NOLINTNEXTLINE + std::string_view resp{reinterpret_cast(slice.ptr), slice.len}; + auto pos_lf = resp.find('\n'); + if (pos_lf == std::string_view::npos) { + throw std::runtime_error{ + "Invalid response from remote config (no newline)"}; + return false; + } + SPDLOG_DEBUG("Runtime id is {}", resp.substr(0, pos_lf)); - const protocol::client_state cs{targets_version_, config_states, - !last_poll_error_.empty(), last_poll_error_, opaque_backend_state_}; - std::vector products_str; - products_str.reserve(products_.size()); - for (const auto &[product_name, product] : products_) { - products_str.push_back(product_name); + std::string_view configs = resp.substr(pos_lf + 1); + while (!configs.empty()) { + auto pos_lf = configs.find('\n'); + if (pos_lf == std::string_view::npos) { + break; + } + new_configs.emplace(config::from_line(configs.substr(0, pos_lf))); + configs = configs.substr(pos_lf + 1); } - protocol::client protocol_client = { - id_, products_str, ct, cs, capabilities_}; - return {std::move(protocol_client), std::move(files)}; -}; + return process_response(std::move(new_configs)); +} -bool client::process_response(const protocol::get_configs_response &response) +bool client::process_response(std::set new_configs) { - if (!response.targets.has_value()) { - return true; - } - - const std::unordered_map paths_on_targets = - response.targets->paths; - const std::unordered_map target_files = - response.target_files; - std::unordered_map> - configs; - for (const std::string &path : response.client_configs) { + for (auto &listener : listeners_) { try { - auto cp = config_path::from_path(path); - - // Is path on targets? - auto path_itr = paths_on_targets.find(path); - if (path_itr == paths_on_targets.end()) { - // Not found - last_poll_error_ = "missing config " + path + " in targets"; - return false; - } - auto length = path_itr->second.length; - std::unordered_map hashes = - path_itr->second.hashes; - int custom_v = path_itr->second.custom_v; - - // Is product on the requested ones? - auto product = products_.find(cp.product); - if (product == products_.end()) { - // Not found - last_poll_error_ = "received config " + path + - " for a product that was not requested"; - return false; - } - - // Is path on target_files? - auto path_in_target_files = target_files.find(path); - std::string raw; - if (path_in_target_files == target_files.end()) { - // Check if file in cache - auto configs_on_product = product->second.get_configs(); - auto config_itr = std::find_if(configs_on_product.begin(), - configs_on_product.end(), [&path, &hashes](auto &pair) { - return pair.second.path == path && - pair.second.hashes == hashes; - }); - - if (config_itr == configs_on_product.end()) { - // Not found - last_poll_error_ = "missing config " + path + - " in target files and in cache files"; - return false; - } - - raw = config_itr->second.contents; - length = config_itr->second.length; - custom_v = config_itr->second.version; - } else { - raw = path_in_target_files->second.raw; - } - - const std::string path_c = path; - config const config_ = { - cp.product, cp.id, raw, path_c, hashes, custom_v, length}; - auto configs_itr = configs.find(cp.product); - if (configs_itr == - configs.end()) { // Product not in configs yet. Create entry - std::unordered_map configs_on_product; - configs_on_product.emplace(cp.id, config_); - configs.insert(std::pair>( - cp.product, configs_on_product)); - } else { // Product already exists in configs. Add new config - configs_itr->second.emplace(cp.id, config_); - } - } catch (invalid_path &e) { - last_poll_error_ = "error parsing path " + path; - return false; + listener->init(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to init listener: {}", e.what()); } } - // Since there have not been errors, we can now update product configs - // First initialise the listener + // unapply should happen first, because asm_dd aggregator ignores the key... + for (const auto &cfg : last_configs_) { + if (new_configs.contains(cfg)) { + continue; + } - for (auto &listener : listeners_) { listener->init(); } + const product p = cfg.get_product(); + auto it = listeners_per_product_.find(p); + if (it == listeners_per_product_.end()) { + continue; + } - for (auto &[name, product] : products_) { - const auto product_configs = configs.find(name); - if (product_configs != configs.end()) { - product.assign_configs(product_configs->second); - } else { - product.assign_configs({}); + SPDLOG_DEBUG("Unapplying config {}", cfg); + for (listener_base *listener : it->second) { + try { + listener->on_unapply(cfg); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to unapply config {}: {}", cfg, e.what()); + } } } - for (auto &listener : listeners_) { listener->commit(); } - - targets_version_ = response.targets->version; - opaque_backend_state_ = response.targets->opaque_backend_state; - - return true; -} - -bool client::is_remote_config_available() -{ - auto response_body = api_->get_info(); - try { - SPDLOG_TRACE("Received info response: {}", response_body); - auto response = protocol::parse_info(response_body); - - return std::find(response.endpoints.begin(), response.endpoints.end(), - "/v0.7/config") != response.endpoints.end(); - } catch (protocol::parser_exception &e) { - SPDLOG_ERROR("Error parsing info response - {}", e.what()); - return false; - } -} + for (const auto &cfg : new_configs) { + const product p = cfg.get_product(); + if (p == known_products::UNKNOWN) { + SPDLOG_INFO("Ignoring config with key {}; unsupported product", + cfg.rc_path); + continue; + } + auto it = listeners_per_product_.find(p); + if (it == listeners_per_product_.end()) { + SPDLOG_INFO( + "No listeners for product {}; skipping key {}", p, cfg.rc_path); + continue; + } -bool client::poll() -{ - // Wait until we have a valid runtime ID, once this ID is available, - // it'll always have a value, even if all extensions have disconnected - if (api_ == nullptr || !ids_.has_value()) { - return false; + SPDLOG_DEBUG("Applying config {}", cfg); + for (listener_base *listener : it->second) { + try { + listener->on_update(cfg); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to apply config {}: {}", cfg, e.what()); + } + } } - auto request = generate_request(); - - std::string serialized_request; - try { - serialized_request = protocol::serialize(request); - SPDLOG_TRACE("Sending request: {}", serialized_request); - } catch (protocol::serializer_exception &e) { - return false; + for (auto &listener : listeners_) { + try { + listener->commit(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to commit listener: {}", e.what()); + } } - auto response_body = api_->get_configs(std::move(serialized_request)); + last_configs_ = std::move(new_configs); - try { - SPDLOG_TRACE("Received response: {}", response_body); - auto response = protocol::parse(response_body); - last_poll_error_.clear(); - return process_response(response); - } catch (protocol::parser_exception &e) { - SPDLOG_ERROR("Error parsing remote config response - {}", e.what()); - return false; - } + return true; } } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/client.hpp b/appsec/src/helper/remote_config/client.hpp index 542d6ff216..c5b22af2c8 100644 --- a/appsec/src/helper/remote_config/client.hpp +++ b/appsec/src/helper/remote_config/client.hpp @@ -6,26 +6,27 @@ #pragma once #include +#include #include #include #include -#include "../service_identifier.hpp" -#include "engine.hpp" -#include "engine_settings.hpp" -#include "http_api.hpp" +#include "../engine.hpp" +#include "../engine_settings.hpp" +#include "../service_config.hpp" +#include "../utils.hpp" #include "listeners/listener.hpp" #include "product.hpp" -#include "protocol/client.hpp" -#include "protocol/tuf/get_configs_request.hpp" -#include "protocol/tuf/get_configs_response.hpp" -#include "runtime_id_pool.hpp" -#include "service_config.hpp" #include "settings.hpp" -#include "utils.hpp" + +extern "C" { +struct ddog_RemoteConfigReader; +} namespace dds::remote_config { +void resolve_symbols(); + struct config_path { static config_path from_path(const std::string &path); @@ -34,63 +35,36 @@ struct config_path { }; class client { + client(remote_config::settings settings, + std::vector> listeners); + public: - using ptr = std::unique_ptr; - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - client(std::unique_ptr &&arg_api, service_identifier &&sid, - remote_config::settings settings, - std::vector listeners = {}); - virtual ~client() = default; + ~client() = default; client(const client &) = delete; client(client &&) = delete; client &operator=(const client &) = delete; client &operator=(client &&) = delete; - static client::ptr from_settings(service_identifier &&sid, + static std::unique_ptr from_settings( const remote_config::settings &settings, - std::vector listeners); - - virtual bool poll(); - virtual bool is_remote_config_available(); - [[nodiscard]] virtual const std::unordered_map & - get_products() - { - return products_; - } - - [[nodiscard]] const service_identifier &get_service_identifier() - { - return sid_; - } - - virtual void register_runtime_id(const std::string &id) { ids_.add(id); } - virtual void unregister_runtime_id(const std::string &id) - { - ids_.remove(id); - } + std::vector> listeners); -protected: - [[nodiscard]] protocol::get_configs_request generate_request() const; - bool process_response(const protocol::get_configs_response &response); - - std::unique_ptr api_; + bool poll(); - std::string id_; - runtime_id_pool ids_; - const service_identifier sid_; - const remote_config::settings settings_; +protected: + bool process_response(std::set new_configs); - // remote config state - std::string last_poll_error_; - std::string opaque_backend_state_; - int targets_version_{0}; + std::unique_ptr + reader_; + remote_config::settings settings_; // just for logging - // supported products - std::vector listeners_; - std::unordered_map products_; + std::vector> listeners_; + std::unordered_map> + listeners_per_product_; // non-owning index of listeners_ - protocol::capabilities_e capabilities_ = {protocol::capabilities_e::NONE}; + std::set last_configs_; }; } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/client_handler.cpp b/appsec/src/helper/remote_config/client_handler.cpp index 4e38f3a38a..d61f999255 100644 --- a/appsec/src/helper/remote_config/client_handler.cpp +++ b/appsec/src/helper/remote_config/client_handler.cpp @@ -13,30 +13,17 @@ namespace dds::remote_config { static constexpr std::chrono::milliseconds default_max_interval = 5min; -client_handler::client_handler(remote_config::client::ptr &&rc_client, - std::shared_ptr service_config, - const std::chrono::milliseconds &poll_interval) - : service_config_(std::move(service_config)), - rc_client_(std::move(rc_client)), poll_interval_(poll_interval), - interval_(poll_interval), max_interval(default_max_interval) -{ - // It starts checking if rc is available - rc_action_ = [this] { discover(); }; -} - -client_handler::~client_handler() -{ - if (handler_.joinable()) { - exit_.set_value(true); - handler_.join(); - } -} +client_handler::client_handler(std::unique_ptr &&rc_client, + std::shared_ptr service_config) + : rc_client_{std::move(rc_client)}, + service_config_{std::move(service_config)} +{} -client_handler::ptr client_handler::from_settings(service_identifier &&id, +std::unique_ptr client_handler::from_settings( const dds::engine_settings &eng_settings, std::shared_ptr service_config, - const remote_config::settings &rc_settings, const engine::ptr &engine_ptr, - bool dynamic_enablement) + const remote_config::settings &rc_settings, + const std::shared_ptr &engine_ptr, bool dynamic_enablement) { if (!rc_settings.enabled) { return {}; @@ -46,7 +33,7 @@ client_handler::ptr client_handler::from_settings(service_identifier &&id, return {}; } - std::vector listeners = {}; + std::vector> listeners = {}; if (dynamic_enablement) { listeners.emplace_back( std::make_shared( @@ -59,41 +46,18 @@ client_handler::ptr client_handler::from_settings(service_identifier &&id, } if (listeners.empty()) { + SPDLOG_DEBUG( + "Not enabling remote config for this service as no " + "listeners are available (no dynamic enablement and no rules " + "file set)"); return {}; } - auto rc_client = remote_config::client::from_settings(std::move(id), - remote_config::settings(rc_settings), std::move(listeners)); + auto rc_client = + remote_config::client::from_settings(rc_settings, std::move(listeners)); - return std::make_shared(std::move(rc_client), - std::move(service_config), - std::chrono::milliseconds{rc_settings.poll_interval}); -} - -bool client_handler::start() -{ - if (rc_client_) { - handler_ = std::thread(&client_handler::run, this, exit_.get_future()); - return true; - } - - return false; -} - -void client_handler::handle_error() -{ - rc_action_ = [this] { discover(); }; - - if (errors_ < std::numeric_limits::max() - 1) { - errors_++; - } - - if (interval_ < max_interval) { - auto new_interval = - std::chrono::duration_cast( - poll_interval_ * pow(2, errors_)); - interval_ = std::min(max_interval, new_interval); - } + return std::make_unique( + std::move(rc_client), std::move(service_config)); } void client_handler::poll() @@ -102,42 +66,6 @@ void client_handler::poll() rc_client_->poll(); } catch (const std::exception &e) { SPDLOG_WARN("Error polling remote config: {}", e.what()); - handle_error(); - } -} -void client_handler::discover() -{ - try { - if (rc_client_->is_remote_config_available()) { - // Remote config is available. Start polls - rc_action_ = [this] { poll(); }; - errors_ = 0; - interval_ = poll_interval_; - return; - } - } catch (const std::exception &e) { - SPDLOG_WARN("Error discovering remote config: {}", e.what()); - } - handle_error(); -} - -void client_handler::tick() { rc_action_(); } - -// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) -void client_handler::run(std::future &&exit_signal) -{ - std::chrono::time_point before{0s}; - std::future_status fs = exit_signal.wait_for(0s); - while (fs == std::future_status::timeout) { - // If the thread is interrupted somehow, make sure to check that - // the polling interval has actually elapsed. - auto now = std::chrono::steady_clock::now(); - if ((now - before) >= interval_) { - tick(); - before = now; - } - - fs = exit_signal.wait_for(interval_); } } } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/client_handler.hpp b/appsec/src/helper/remote_config/client_handler.hpp index 4fea0e2a06..2a88649b00 100644 --- a/appsec/src/helper/remote_config/client_handler.hpp +++ b/appsec/src/helper/remote_config/client_handler.hpp @@ -5,17 +5,11 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "engine.hpp" -#include "remote_config/client.hpp" -#include "remote_config/settings.hpp" -#include "service_config.hpp" -#include "service_identifier.hpp" -#include "std_logging.hpp" -#include "utils.hpp" -#include +#include "../engine.hpp" +#include "../service_config.hpp" +#include "client.hpp" +#include "settings.hpp" #include -#include -#include namespace dds::remote_config { @@ -23,12 +17,9 @@ using namespace std::chrono_literals; class client_handler { public: - using ptr = std::shared_ptr; - - client_handler(remote_config::client::ptr &&rc_client, - std::shared_ptr service_config, - const std::chrono::milliseconds &poll_interval = 1s); - ~client_handler(); + client_handler(std::unique_ptr &&rc_client, + std::shared_ptr service_config); + ~client_handler() = default; client_handler(const client_handler &) = delete; client_handler &operator=(const client_handler &) = delete; @@ -36,48 +27,17 @@ class client_handler { client_handler(client_handler &&) = delete; client_handler &operator=(client_handler &&) = delete; - static client_handler::ptr from_settings(service_identifier &&id, + static std::unique_ptr from_settings( const dds::engine_settings &eng_settings, std::shared_ptr service_config, const remote_config::settings &rc_settings, - const engine::ptr &engine_ptr, bool dynamic_enablement); - - bool start(); - - remote_config::client *get_client() { return rc_client_.get(); } + const std::shared_ptr &engine_ptr, bool dynamic_enablement); - void register_runtime_id(const std::string &id) - { - if (rc_client_) { - rc_client_->register_runtime_id(id); - } - } - void unregister_runtime_id(const std::string &id) - { - if (rc_client_) { - rc_client_->unregister_runtime_id(id); - } - } + void poll(); protected: - void run(std::future &&exit_signal); - void handle_error(); - - remote_config::client::ptr rc_client_; std::shared_ptr service_config_; - - std::chrono::milliseconds poll_interval_; - std::chrono::milliseconds interval_; - std::chrono::milliseconds max_interval; - void poll(); - void discover(); - void tick(); - std::function rc_action_; - - std::uint16_t errors_ = {0}; - - std::promise exit_; - std::thread handler_; + std::unique_ptr rc_client_; }; } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/config.cpp b/appsec/src/helper/remote_config/config.cpp new file mode 100644 index 0000000000..7914ba5e6f --- /dev/null +++ b/appsec/src/helper/remote_config/config.cpp @@ -0,0 +1,118 @@ +#include "config.hpp" +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +using namespace std::literals; + +namespace dds::remote_config { + +[[nodiscard]] product config::get_product() const +{ + // A configuration key has the form: + // (datadog/ | employee)///" + std::string_view sv{rc_path}; + if (sv.starts_with("datadog/"sv)) { + sv.remove_prefix("datadog/"sv.length()); + auto org_id_end = sv.find('/'); + if (org_id_end != std::string_view::npos) { + sv.remove_prefix(org_id_end + 1); + auto product_end = sv.find('/'); + if (product_end != std::string_view::npos) { + return known_products::for_name(sv.substr(0, product_end)); + } + } + } else if (sv.starts_with("employee/"sv)) { + sv.remove_prefix("employee/"sv.length()); + auto product_end = shm_path.find('/'); + if (product_end != std::string::npos) { + return known_products::for_name(sv.substr(0, product_end)); + } + } + + return known_products::UNKNOWN; +} + +config config::from_line(std::string_view line) +{ + // split by : + auto pos = line.find(':'); + if (pos == std::string_view::npos) { + throw std::runtime_error{"invalid shmem config line (no colon)"}; + } + + const std::string_view shm_path{line.substr(0, pos)}; + auto pos2 = line.find(':', pos + 1); + if (pos2 == std::string_view::npos) { + throw std::runtime_error{"invalid shmem config line (no second colon)"}; + } + + std::uint32_t limiter_idx; + auto res = + std::from_chars(line.data() + pos + 1, line.data() + pos2, limiter_idx); + if (res.ec != std::errc{} || res.ptr != line.data() + pos2) { + throw std::runtime_error{"invalid shmem config line (limiter_idx)"}; + } + + const std::string_view rc_path_encoded{line.substr(pos2 + 1)}; + // base64 decode rc_path (no padding): + std::string rc_path = base64_decode(rc_path_encoded); + + return {std::string{shm_path}, std::move(rc_path)}; +} + +std::string config::read() const +{ + // open shared memory segment at rc_path: + const int fd = ::shm_open(shm_path.c_str(), O_RDONLY, 0); + if (fd == -1) { + throw std::runtime_error{ + "shm_open failed for " + shm_path + " : " + strerror_ts(errno)}; + } + + auto close_fs = defer{[fd]() { ::close(fd); }}; + + // check that the uid of the shared memory segment is the same as ours + struct ::stat shm_stat {}; + if (::fstat(fd, &shm_stat) == -1) { + throw std::runtime_error{ + "Call to fstat on memory segment failed: " + strerror_ts(errno)}; + } + if (shm_stat.st_uid != ::getuid()) { + throw std::runtime_error{"Shared memory segment does not have the " + "expected owner. Expect our uid " + + std::to_string(::getuid()) + " but found " + + std::to_string(shm_stat.st_uid)}; + } + + void *shm_ptr = + ::mmap(nullptr, shm_stat.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (shm_ptr == MAP_FAILED) { // NOLINT + throw std::runtime_error( + "Failed to map shared memory: " + std::string{strerror_ts(errno)}); + } + + auto unmap = defer{[shm_ptr, &shm_stat]() { + if (::munmap(shm_ptr, shm_stat.st_size) == -1) { + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_WARN( + "Failed to unmap shared memory: {}", strerror_ts(errno)); + } + }}; + + std::string result; + result.resize(shm_stat.st_size); + + std::copy_n(static_cast(shm_ptr), shm_stat.st_size, result.begin()); + + return result; +} +} // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/config.hpp b/appsec/src/helper/remote_config/config.hpp index 839c51e6f3..cf3b615f51 100644 --- a/appsec/src/helper/remote_config/config.hpp +++ b/appsec/src/helper/remote_config/config.hpp @@ -5,48 +5,50 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "protocol/config_state.hpp" -#include +#include "../utils.hpp" +#include "product.hpp" #include -#include +#include #include namespace dds::remote_config { struct config { - std::string product; - std::string id; - std::string contents; - std::string path; - std::unordered_map hashes; - int version; - int length; - protocol::config_state::applied_state apply_state; - std::string apply_error; - - friend auto &operator<<( - std::ostream &os, const dds::remote_config::config &c) + // from a line provided by the RC config reader + static config from_line(std::string_view line); + + std::string shm_path; + std::string rc_path; + + [[nodiscard]] std::string read() const; + + [[nodiscard]] product get_product() const; + + bool operator==(const config &b) const { - os << "Product: " << c.product << std::endl; - os << "id: " << c.id << std::endl; - os << "contents: " << c.contents << std::endl; - os << "path: " << c.path << std::endl; - // os << "hashes: " << c.hashes << std::endl; - os << "version: " << c.version << std::endl; - os << "length: " << c.length << std::endl; - os << "apply_state: " << (int)c.apply_state << std::endl; - os << "apply_error: " << c.apply_error << std::endl; - return os; + return shm_path == b.shm_path && rc_path == b.rc_path; } -}; -inline bool operator==(const config &rhs, const config &lhs) -{ - return rhs.product == lhs.product && rhs.id == lhs.id && - rhs.contents == lhs.contents && rhs.hashes == lhs.hashes && - rhs.version == lhs.version && rhs.path == lhs.path && - rhs.length == lhs.length && rhs.apply_state == lhs.apply_state && - rhs.apply_error == lhs.apply_error; -} + friend std::ostream &operator<<(std::ostream &os, const config &c) + { + return os << c.shm_path << ":" << c.rc_path; + } +}; } // namespace dds::remote_config + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::remote_config::config &key) const + { + return dds::hash(key.shm_path, key.rc_path); + } +}; +template <> struct less { + bool operator()(const dds::remote_config::config &lhs, + const dds::remote_config::config &rhs) const + { + return lhs.rc_path < rhs.rc_path; + } +}; +} // namespace std diff --git a/appsec/src/helper/remote_config/http_api.cpp b/appsec/src/helper/remote_config/http_api.cpp deleted file mode 100644 index 70044c8f9b..0000000000 --- a/appsec/src/helper/remote_config/http_api.cpp +++ /dev/null @@ -1,147 +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 "http_api.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast = boost::beast; // from -namespace http = beast::http; // from -namespace net = boost::asio; // from -using tcp = net::ip::tcp; // from - -namespace { -constexpr auto timeout = - std::chrono::duration_cast( - std::chrono::seconds{60}); -const int version = 11; - -// NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) -net::awaitable execute_request(const std::string &host, - // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) - const std::string &port, const http::request &request) -{ - std::string result; - - try { - auto exec = co_await net::this_coro::executor; - - // These objects perform our I/O - tcp::resolver resolver(exec); - beast::tcp_stream stream(exec); - - // Look up the domain name - auto const results = - co_await resolver.async_resolve(host, port, net::use_awaitable); - - // Make the connection on the IP address we get from a lookup - beast::get_lowest_layer(stream).expires_after(timeout); - co_await stream.async_connect( - results.begin(), results.end(), net::use_awaitable); - - // Send the HTTP request to the remote host - co_await http::async_write(stream, request, net::use_awaitable); - - // This buffer is used for reading and must be persisted - beast::flat_buffer buffer; - - // Declare a container to hold the response - http::response res; - - // Receive the HTTP response - co_await http::async_read(stream, buffer, res, net::use_awaitable); - - // Write the message to standard out - result = boost::beast::buffers_to_string(res.body().data()); - - // Gracefully close the socket - beast::error_code ec; - // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c) - stream.socket().shutdown(tcp::socket::shutdown_both, ec); - - // not_connected happens sometimes - // so don't bother reporting it. - // - if (ec && ec != beast::errc::not_connected) { - throw beast::system_error{ec}; - } - - // If we get here then the connection is closed gracefully - } catch (std::exception const &e) { - auto sv = request.target(); - const std::string err{sv.data(), sv.size()}; - SPDLOG_ERROR("Connection error - {} - {}", err, e.what()); - throw dds::remote_config::network_exception( - "Connection error - " + err + " - " + e.what()); - } - - co_return result; -} - -std::string execute_request_sync(const std::string &host, - const std::string &port, const http::request &req) -{ - - net::io_context ioc; - net::awaitable client_coroutine = - execute_request(host, port, req); - - std::promise promise; - auto fut = promise.get_future(); - - net::co_spawn(ioc, std::move(client_coroutine), - [&](const std::exception_ptr &eptr, std::string body) { - if (eptr) { - promise.set_exception(eptr); - } else { - promise.set_value(std::move(body)); - } - }); - - ioc.run(); - return fut.get(); -} -} // namespace - -std::string dds::remote_config::http_api::get_info() const -{ - http::request req{http::verb::get, "/info", version}; - req.set(http::field::host, host_); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - - return execute_request_sync(host_, port_, req); -} - -std::string dds::remote_config::http_api::get_configs( - std::string &&request) const -{ - // Set up an HTTP POST request message - http::request req; - req.method(http::verb::post); - req.target("/v0.7/config"); - req.version(version); - req.set(http::field::host, host_); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - req.set(http::field::content_length, std::to_string(request.size())); - req.set(http::field::accept, "*/*"); - req.set(http::field::content_type, "application/x-www-form-urlencoded"); - req.body() = std::move(request); - req.keep_alive(true); - - return execute_request_sync(host_, port_, req); -}; diff --git a/appsec/src/helper/remote_config/http_api.hpp b/appsec/src/helper/remote_config/http_api.hpp deleted file mode 100644 index f83fb2e527..0000000000 --- a/appsec/src/helper/remote_config/http_api.hpp +++ /dev/null @@ -1,47 +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 - -namespace dds::remote_config { - -class network_exception : public std::exception { -public: - explicit network_exception(std::string what) : what_(std::move(what)) {} - [[nodiscard]] const char *what() const noexcept override - { - return what_.c_str(); - } - -protected: - const std::string what_; -}; - -class http_api { -public: - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - http_api(std::string host, std::string port) - : host_(std::move(host)), port_(std::move(port)){}; - - http_api(const http_api &) = delete; - http_api(http_api &&) = delete; - - http_api &operator=(const http_api &) = delete; - http_api &operator=(http_api &&) = delete; - - virtual ~http_api() = default; - - virtual std::string get_info() const; - virtual std::string get_configs(std::string &&request) const; - -protected: - std::string host_; - std::string port_; -}; - -} // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp b/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp index edd8ae0f66..5b6cd791de 100644 --- a/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp +++ b/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp @@ -4,18 +4,17 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_features_listener.hpp" -#include "exception.hpp" -#include "json_helper.hpp" -#include "remote_config/exception.hpp" -#include "utils.hpp" +#include "../../json_helper.hpp" +#include "../../utils.hpp" +#include "../exception.hpp" #include #include void dds::remote_config::asm_features_listener::on_update(const config &config) { + const std::string contents{config.read()}; rapidjson::Document serialized_doc; - if (!json_helper::get_json_base64_encoded_content( - config.contents, serialized_doc)) { + if (!json_helper::parse_json(contents, serialized_doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp b/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp index ab91cc42be..9ca500549c 100644 --- a/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp +++ b/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp @@ -5,9 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../config.hpp" +#include "../../service_config.hpp" +#include "../product.hpp" #include "listener.hpp" -#include "service_config.hpp" namespace dds::remote_config { @@ -22,10 +23,9 @@ class asm_features_listener : public listener_base { service_config_->unset_asm(); } - [[nodiscard]] std::unordered_map - get_supported_products() override + [[nodiscard]] std::unordered_set get_supported_products() override { - return {{"ASM_FEATURES", protocol::capabilities_e::ASM_ACTIVATION}}; + return {known_products::ASM_FEATURES}; } void init() override {} diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp index 0fb56fd106..f8d5f3b9b1 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp @@ -4,12 +4,11 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_aggregator.hpp" -#include "exception.hpp" -#include "remote_config/exception.hpp" -#include "spdlog/spdlog.h" +#include "../../exception.hpp" #include #include #include +#include namespace dds::remote_config { @@ -31,7 +30,7 @@ void asm_aggregator::init(rapidjson::Document::AllocatorType *allocator) void asm_aggregator::add(const config &config) { rapidjson::Document doc(&ruleset_.GetAllocator()); - if (!json_helper::get_json_base64_encoded_content(config.contents, doc)) { + if (!json_helper::parse_json(config.read(), doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp index 46dfdf4b6a..d4998f3836 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp @@ -5,11 +5,11 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../../config.hpp" +#include "../../../engine.hpp" +#include "../../../json_helper.hpp" +#include "../../../parameter.hpp" #include "config_aggregator.hpp" -#include "engine.hpp" -#include "json_helper.hpp" -#include "parameter.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp index 90ef911524..b0f6805872 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp @@ -4,13 +4,12 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_data_aggregator.hpp" -#include "exception.hpp" -#include "json_helper.hpp" -#include "remote_config/exception.hpp" -#include "spdlog/spdlog.h" +#include "../../../json_helper.hpp" +#include "../../exception.hpp" #include #include #include +#include namespace dds::remote_config { @@ -81,8 +80,7 @@ void extract_data( void asm_data_aggregator::add(const config &config) { rapidjson::Document serialized_doc; - if (!json_helper::get_json_base64_encoded_content( - config.contents, serialized_doc)) { + if (!json_helper::parse_json(config.read(), serialized_doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp index 4dba85460f..a570c6eea4 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp @@ -5,10 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../../engine.hpp" +#include "../../../parameter.hpp" +#include "../../config.hpp" #include "config_aggregator.hpp" -#include "engine.hpp" -#include "parameter.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp index ab5c8ace09..ec0458b34a 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp @@ -4,14 +4,14 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_dd_aggregator.hpp" -#include "exception.hpp" -#include "remote_config/exception.hpp" +#include "../../../exception.hpp" +#include "../../exception.hpp" #include void dds::remote_config::asm_dd_aggregator::add(const config &config) { rapidjson::Document doc(&ruleset_.GetAllocator()); - if (!json_helper::get_json_base64_encoded_content(config.contents, doc)) { + if (!json_helper::parse_json(config.read(), doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp index 937a4d1c69..2951005e71 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp @@ -5,10 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../../config.hpp" +#include "../../../json_helper.hpp" +#include "../../../parameter.hpp" #include "config_aggregator.hpp" -#include "json_helper.hpp" -#include "parameter.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp index 52bcd41e08..381eed0bb0 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp @@ -5,10 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" -#include "engine.hpp" -#include "parameter.hpp" -#include "remote_config/listeners/listener.hpp" +#include "../../../config.hpp" +#include "../../../engine.hpp" +#include "../../../parameter.hpp" +#include "../listener.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/engine_listener.cpp b/appsec/src/helper/remote_config/listeners/engine_listener.cpp index 761f6be0e8..29d92f1c6e 100644 --- a/appsec/src/helper/remote_config/listeners/engine_listener.cpp +++ b/appsec/src/helper/remote_config/listeners/engine_listener.cpp @@ -4,29 +4,30 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "engine_listener.hpp" +#include "../../json_helper.hpp" +#include "../exception.hpp" +#include "../product.hpp" #include "config_aggregators/asm_aggregator.hpp" #include "config_aggregators/asm_data_aggregator.hpp" #include "config_aggregators/asm_dd_aggregator.hpp" -#include "exception.hpp" -#include "json_helper.hpp" -#include "remote_config/exception.hpp" -#include "spdlog/spdlog.h" #include #include #include +#include #include namespace dds::remote_config { engine_listener::engine_listener( - engine::ptr engine, const std::string &rules_file) + std::shared_ptr engine, const std::string &rules_file) : engine_(std::move(engine)) { - aggregators_.emplace(asm_product, std::make_unique()); aggregators_.emplace( - asm_dd_product, std::make_unique(rules_file)); + known_products::ASM, std::make_unique()); + aggregators_.emplace(known_products::ASM_DD, + std::make_unique(rules_file)); aggregators_.emplace( - asm_data_product, std::make_unique()); + known_products::ASM_DATA, std::make_unique()); } void engine_listener::init() @@ -37,9 +38,10 @@ void engine_listener::init() void engine_listener::on_update(const config &config) { - auto it = aggregators_.find(config.product); + auto it = aggregators_.find(config.get_product()); if (it == aggregators_.end()) { - throw error_applying_config("unknown product: " + config.product); + throw error_applying_config( + "unknown product: " + std::string{config.get_product().name()}); } auto &aggregator = it->second; @@ -53,9 +55,10 @@ void engine_listener::on_update(const config &config) void engine_listener::on_unapply(const config &config) { - auto it = aggregators_.find(config.product); + auto it = aggregators_.find(config.get_product()); if (it == aggregators_.end()) { - throw error_applying_config("unknown product: " + config.product); + throw error_applying_config( + "unknown product: " + std::string{config.get_product().name()}); } auto &aggregator = it->second; diff --git a/appsec/src/helper/remote_config/listeners/engine_listener.hpp b/appsec/src/helper/remote_config/listeners/engine_listener.hpp index 1ef7847ed5..6f961b6c2c 100644 --- a/appsec/src/helper/remote_config/listeners/engine_listener.hpp +++ b/appsec/src/helper/remote_config/listeners/engine_listener.hpp @@ -5,12 +5,12 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../config.hpp" +#include "../../engine.hpp" +#include "../../parameter.hpp" +#include "../product.hpp" #include "config_aggregators/config_aggregator.hpp" -#include "engine.hpp" #include "listener.hpp" -#include "parameter.hpp" -#include "remote_config/protocol/client.hpp" #include #include #include @@ -21,7 +21,7 @@ namespace dds::remote_config { class engine_listener : public listener_base { public: explicit engine_listener( - engine::ptr engine, const std::string &rules_file = {}); + std::shared_ptr engine, const std::string &rules_file = {}); engine_listener(const engine_listener &) = delete; engine_listener(engine_listener &&) = default; engine_listener &operator=(const engine_listener &) = delete; @@ -34,30 +34,16 @@ class engine_listener : public listener_base { void on_unapply(const config &config) override; void commit() override; - [[nodiscard]] std::unordered_map - get_supported_products() override + [[nodiscard]] std::unordered_set get_supported_products() override { - return {{asm_product, - protocol::capabilities_e::ASM_EXCLUSIONS | - protocol::capabilities_e::ASM_CUSTOM_BLOCKING_RESPONSE | - protocol::capabilities_e::ASM_REQUEST_BLOCKING | - protocol::capabilities_e::ASM_RESPONSE_BLOCKING | - protocol::capabilities_e::ASM_CUSTOM_RULES | - protocol::capabilities_e::ASM_TRUSTED_IPS}, - {asm_dd_product, protocol::capabilities_e::ASM_DD_RULES}, - {asm_data_product, - protocol::capabilities_e::ASM_IP_BLOCKING | - protocol::capabilities_e::ASM_USER_BLOCKING}}; + return {known_products::ASM, known_products::ASM_DD, + known_products::ASM_DATA}; } protected: - static constexpr std::string_view asm_product = "ASM"; - static constexpr std::string_view asm_dd_product = "ASM_DD"; - static constexpr std::string_view asm_data_product = "ASM_DATA"; - - std::unordered_map + std::unordered_map aggregators_; - engine::ptr engine_; + std::shared_ptr engine_; rapidjson::Document ruleset_; std::unordered_set to_commit_; }; diff --git a/appsec/src/helper/remote_config/listeners/listener.hpp b/appsec/src/helper/remote_config/listeners/listener.hpp index df17d900e5..91bb93af1a 100644 --- a/appsec/src/helper/remote_config/listeners/listener.hpp +++ b/appsec/src/helper/remote_config/listeners/listener.hpp @@ -5,17 +5,17 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "remote_config/config.hpp" -#include "remote_config/protocol/client.hpp" +#include "../config.hpp" +#include "../product.hpp" #include +#include +#include #include namespace dds::remote_config { class listener_base { public: - using shared_ptr = std::shared_ptr; - listener_base() = default; listener_base(const listener_base &) = default; listener_base(listener_base &&) = default; @@ -25,8 +25,7 @@ class listener_base { virtual void on_update(const config &config) = 0; virtual void on_unapply(const config &config) = 0; - [[nodiscard]] virtual std::unordered_map + [[nodiscard]] virtual std::unordered_set get_supported_products() = 0; // Stateful listeners need to override these methods diff --git a/appsec/src/helper/remote_config/product.cpp b/appsec/src/helper/remote_config/product.cpp deleted file mode 100644 index 2a604c62d0..0000000000 --- a/appsec/src/helper/remote_config/product.cpp +++ /dev/null @@ -1,83 +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 "product.hpp" -#include "exception.hpp" - -void dds::remote_config::product::update_configs( - std::unordered_map &to_update) -{ - for (auto &[name, config] : to_update) { - try { - listener_->on_update(config); - config.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ACKNOWLEDGED; - config.apply_error = ""; - } catch (dds::remote_config::error_applying_config &e) { - config.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ERROR; - config.apply_error = e.what(); - } - } -} - -void dds::remote_config::product::unapply_configs( - std::unordered_map &to_unapply) -{ - for (auto &[path, conf] : to_unapply) { - try { - listener_->on_unapply(conf); - conf.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ACKNOWLEDGED; - conf.apply_error = ""; - } catch (dds::remote_config::error_applying_config &e) { - conf.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ERROR; - conf.apply_error = e.what(); - } - } -} - -void dds::remote_config::product::assign_configs( - const std::unordered_map &configs) -{ - std::unordered_map to_update; - bool changes = false; - - // determine what each config given is - for (const auto &[name, config] : configs) { - auto previous_config = configs_.find(name); - if (previous_config == configs_.end()) { // New config - changes = true; - auto config_to_update = config; - config_to_update.apply_state = dds::remote_config::protocol:: - config_state::applied_state::UNACKNOWLEDGED; - to_update.emplace(name, config_to_update); - } else { // Already existed - if (config.hashes == - previous_config->second.hashes) { // No changes in config - to_update.emplace(name, previous_config->second); - } else { // Config updated - changes = true; - auto config_to_update = config; - config_to_update.apply_state = dds::remote_config::protocol:: - config_state::applied_state::UNACKNOWLEDGED; - to_update.emplace(name, config_to_update); - } - // configs_ at the end of this loop will contain only configs - // which have to be unapply. This one has been classified as - // something else and therefore, it has to be removed - configs_.erase(previous_config); - } - } - - if (changes || !configs_.empty()) { - update_configs(to_update); - unapply_configs(configs_); - } - - // Save new state of configs - configs_ = std::move(to_update); -}; diff --git a/appsec/src/helper/remote_config/product.hpp b/appsec/src/helper/remote_config/product.hpp index 5c04f14904..a9b70fe60f 100644 --- a/appsec/src/helper/remote_config/product.hpp +++ b/appsec/src/helper/remote_config/product.hpp @@ -5,50 +5,62 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" -#include "listeners/listener.hpp" -#include "remote_config/protocol/client.hpp" -#include -#include -#include -#include -#include -#include +#include "../config.hpp" +#include "../utils.hpp" +#include namespace dds::remote_config { class product { public: - explicit product(std::string_view name, listener_base::shared_ptr listener) - : name_(name), listener_(std::move(listener)) + explicit constexpr product(std::string_view name) : name_{name} {} + + [[nodiscard]] const std::string_view &name() const { return name_; } + + bool operator==(const product &other) const { return name_ == other.name_; } + + friend std::ostream &operator<<(std::ostream &os, const product &p) { - if (listener_ == nullptr) { - throw std::runtime_error("invalid listener"); - } + return os << p.name_; } - void assign_configs(const std::unordered_map &configs); - [[nodiscard]] const std::unordered_map & - get_configs() const - { - return configs_; - }; - bool operator==(product const &b) const +private: + std::string_view name_; +}; + +struct known_products { + static inline constexpr product ASM{std::string_view{"ASM"}}; + static inline constexpr product ASM_DD{std::string_view{"ASM_DD"}}; + static inline constexpr product ASM_DATA{std::string_view{"ASM_DATA"}}; + static inline constexpr product ASM_FEATURES{ + std::string_view{"ASM_FEATURES"}}; + static inline constexpr product UNKNOWN{std::string_view{"UNKOWN"}}; + + static product for_name(std::string_view name) { - return name_ == b.name_ && configs_ == b.configs_; + if (name == ASM.name()) { + return ASM; + } + if (name == ASM_DD.name()) { + return ASM_DD; + } + if (name == ASM_DATA.name()) { + return ASM_DATA; + } + if (name == ASM_FEATURES.name()) { + return ASM_FEATURES; + } + + return UNKNOWN; } - [[nodiscard]] const std::string &get_name() const { return name_; } - -protected: - void update_configs( - std::unordered_map &to_update); - void unapply_configs( - std::unordered_map - &to_unapply); - - std::string name_; - std::unordered_map configs_; - std::shared_ptr listener_; }; - } // namespace dds::remote_config + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::remote_config::product &product) const + { + return dds::hash(product.name()); + } +}; +} // namespace std diff --git a/appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp b/appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp deleted file mode 100644 index 84a4b6dc2e..0000000000 --- a/appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp +++ /dev/null @@ -1,22 +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 - -namespace dds::remote_config::protocol { - -struct cached_target_files_hash { - std::string algorithm; - std::string hash; -}; - -inline bool operator==( - const cached_target_files_hash &rhs, const cached_target_files_hash &lhs) -{ - return rhs.algorithm == lhs.algorithm && rhs.hash == lhs.hash; -} -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/cached_target_files.hpp b/appsec/src/helper/remote_config/protocol/cached_target_files.hpp deleted file mode 100644 index dfda0c496b..0000000000 --- a/appsec/src/helper/remote_config/protocol/cached_target_files.hpp +++ /dev/null @@ -1,27 +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 "cached_target_file_hash.hpp" - -namespace dds::remote_config::protocol { - -struct cached_target_files { - std::string path; - int length; - std::vector hashes; -}; - -inline bool operator==( - const cached_target_files &rhs, const cached_target_files &lhs) -{ - return rhs.path == lhs.path && rhs.length == lhs.length && - rhs.hashes == lhs.hashes; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/client.hpp b/appsec/src/helper/remote_config/protocol/client.hpp deleted file mode 100644 index 1e13d08a7c..0000000000 --- a/appsec/src/helper/remote_config/protocol/client.hpp +++ /dev/null @@ -1,70 +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 "client_state.hpp" -#include "client_tracer.hpp" - -namespace dds::remote_config::protocol { - -enum class capabilities_e : uint16_t { - NONE = 0, - RESERVED = 1, - ASM_ACTIVATION = 1 << 1, - ASM_IP_BLOCKING = 1 << 2, - ASM_DD_RULES = 1 << 3, - ASM_EXCLUSIONS = 1 << 4, - ASM_REQUEST_BLOCKING = 1 << 5, - ASM_RESPONSE_BLOCKING = 1 << 6, - ASM_USER_BLOCKING = 1 << 7, - ASM_CUSTOM_RULES = 1 << 8, - ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9, - ASM_TRUSTED_IPS = 1 << 10, -}; - -constexpr capabilities_e operator|( - const capabilities_e &lhs, capabilities_e rhs) -{ - return static_cast( - static_cast::type>(lhs) | - static_cast::type>(rhs)); -} - -constexpr capabilities_e &operator|=( - capabilities_e &lhs, const capabilities_e rhs) -{ - lhs = lhs | rhs; - return lhs; -} - -constexpr capabilities_e operator&(capabilities_e lhs, capabilities_e rhs) -{ - return static_cast( - static_cast::type>(lhs) & - static_cast::type>(rhs)); -} - -struct client { - std::string id; - std::vector products; - protocol::client_tracer client_tracer; - protocol::client_state client_state; - capabilities_e capabilities; -}; - -inline bool operator==(const client &rhs, const client &lhs) -{ - return rhs.id == lhs.id && rhs.products == lhs.products && - rhs.client_tracer == lhs.client_tracer && - rhs.client_state == lhs.client_state && - rhs.capabilities == lhs.capabilities; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/client_state.hpp b/appsec/src/helper/remote_config/protocol/client_state.hpp deleted file mode 100644 index 369736ce8e..0000000000 --- a/appsec/src/helper/remote_config/protocol/client_state.hpp +++ /dev/null @@ -1,30 +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 "config_state.hpp" -#include -#include - -namespace dds::remote_config::protocol { - -struct client_state { - int targets_version; - std::vector config_states; - bool has_error; - std::string error; - std::string backend_client_state; -}; - -inline bool operator==(const client_state &rhs, const client_state &lhs) -{ - return rhs.targets_version == lhs.targets_version && - rhs.config_states == lhs.config_states && - rhs.has_error == lhs.has_error && rhs.error == lhs.error && - rhs.backend_client_state == lhs.backend_client_state; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/client_tracer.hpp b/appsec/src/helper/remote_config/protocol/client_tracer.hpp deleted file mode 100644 index 8fb4313583..0000000000 --- a/appsec/src/helper/remote_config/protocol/client_tracer.hpp +++ /dev/null @@ -1,31 +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 - -namespace dds::remote_config::protocol { - -struct client_tracer { - std::string runtime_id; - std::string tracer_version; - std::string service; - std::vector extra_services; - std::string env; - std::string app_version; -}; - -inline bool operator==(const client_tracer &rhs, const client_tracer &lhs) -{ - return rhs.runtime_id == lhs.runtime_id && - rhs.tracer_version == lhs.tracer_version && - rhs.service == lhs.service && - rhs.extra_services == lhs.extra_services && rhs.env == lhs.env && - rhs.app_version == lhs.app_version; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/config_state.hpp b/appsec/src/helper/remote_config/protocol/config_state.hpp deleted file mode 100644 index 791a895668..0000000000 --- a/appsec/src/helper/remote_config/protocol/config_state.hpp +++ /dev/null @@ -1,32 +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 - -namespace dds::remote_config::protocol { - -struct config_state { - std::string id; - int version; - std::string product; - enum class applied_state : int { - UNKNOWN = 0, - UNACKNOWLEDGED = 1, - ACKNOWLEDGED = 2, - ERROR = 3 - } apply_state; - std::string apply_error; -}; - -inline bool operator==(const config_state &rhs, const config_state &lhs) -{ - return rhs.id == lhs.id && rhs.version == lhs.version && - rhs.product == lhs.product && rhs.apply_state == lhs.apply_state && - rhs.apply_error == lhs.apply_error; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/path.hpp b/appsec/src/helper/remote_config/protocol/path.hpp deleted file mode 100644 index a902efa0ae..0000000000 --- a/appsec/src/helper/remote_config/protocol/path.hpp +++ /dev/null @@ -1,26 +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 - -namespace dds::remote_config::protocol { - -struct path { - int custom_v; - std::unordered_map hashes; - int length; -}; - -inline bool operator==(const path &rhs, const path &lhs) -{ - return rhs.custom_v == lhs.custom_v && rhs.hashes == lhs.hashes && - rhs.length == lhs.length; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/target_file.hpp b/appsec/src/helper/remote_config/protocol/target_file.hpp deleted file mode 100644 index 170f742ddd..0000000000 --- a/appsec/src/helper/remote_config/protocol/target_file.hpp +++ /dev/null @@ -1,23 +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 - -namespace dds::remote_config::protocol { - -struct target_file { - std::string path; - std::string raw; -}; - -inline bool operator==(const target_file &rhs, const target_file &lhs) -{ - return rhs.path == lhs.path && rhs.raw == lhs.raw; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/targets.hpp b/appsec/src/helper/remote_config/protocol/targets.hpp deleted file mode 100644 index 2844c25954..0000000000 --- a/appsec/src/helper/remote_config/protocol/targets.hpp +++ /dev/null @@ -1,28 +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 "path.hpp" - -namespace dds::remote_config::protocol { - -struct targets { - int version; - std::string opaque_backend_state; - std::unordered_map paths; -}; - -inline bool operator==(const targets &rhs, const targets &lhs) -{ - return rhs.version == lhs.version && - rhs.opaque_backend_state == lhs.opaque_backend_state && - std::equal(lhs.paths.begin(), lhs.paths.end(), rhs.paths.begin()); -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp b/appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp deleted file mode 100644 index dd7a6523f2..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp +++ /dev/null @@ -1,28 +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 "../cached_target_files.hpp" -#include "../client.hpp" - -namespace dds::remote_config::protocol { - -struct get_configs_request { -public: - protocol::client client; - std::vector cached_target_files; -}; - -inline bool operator==( - const get_configs_request &rhs, const get_configs_request &lhs) -{ - return rhs.client == lhs.client && - rhs.cached_target_files == lhs.cached_target_files; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp b/appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp deleted file mode 100644 index 19a68e37f9..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp +++ /dev/null @@ -1,24 +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 "../cached_target_files.hpp" -#include "../client.hpp" -#include "../target_file.hpp" -#include "../targets.hpp" - -namespace dds::remote_config::protocol { - -struct get_configs_response { - std::unordered_map target_files; - std::vector client_configs; - std::optional targets; -}; - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/info_response.hpp b/appsec/src/helper/remote_config/protocol/tuf/info_response.hpp deleted file mode 100644 index 5e47531f62..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/info_response.hpp +++ /dev/null @@ -1,22 +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 "../cached_target_files.hpp" -#include "../client.hpp" -#include "../target_file.hpp" -#include "../targets.hpp" - -namespace dds::remote_config::protocol { - -struct info_response { - std::vector endpoints; -}; - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/parser.cpp b/appsec/src/helper/remote_config/protocol/tuf/parser.cpp deleted file mode 100644 index 4e327fb5f1..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/parser.cpp +++ /dev/null @@ -1,357 +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 "parser.hpp" -#include -#include - -using namespace std::literals; - -namespace dds::remote_config::protocol { - -bool validate_field_is_present(const rapidjson::Value &parent_field, - const char *key, rapidjson::Type type, - rapidjson::Value::ConstMemberIterator &output_itr, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result missing, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result invalid) -{ - output_itr = parent_field.FindMember(key); - - if (output_itr == parent_field.MemberEnd()) { - if (missing == remote_config_parser_result::allow_missing) { - return false; - } - throw parser_exception(missing); - } - - if (type == output_itr->value.GetType()) { - return true; - } - throw parser_exception(invalid); -} - -bool validate_field_is_present( - rapidjson::Value::ConstMemberIterator &parent_field, const char *key, - rapidjson::Type type, rapidjson::Value::ConstMemberIterator &output_itr, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result &missing, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result &invalid) -{ - output_itr = parent_field->value.FindMember(key); - - if (output_itr == parent_field->value.MemberEnd()) { - throw parser_exception(missing); - } - - if (type == output_itr->value.GetType()) { - return true; - } - - throw parser_exception(invalid); -} - -std::unordered_map parse_target_files( - rapidjson::Value::ConstMemberIterator target_files_itr) -{ - std::unordered_map result; - - for (rapidjson::Value::ConstValueIterator itr = - target_files_itr->value.Begin(); - itr != target_files_itr->value.End(); ++itr) { - if (!itr->IsObject()) { - throw parser_exception( - remote_config_parser_result::target_files_object_invalid); - } - - // Path checks - rapidjson::Value::ConstMemberIterator const path_itr = - itr->GetObject().FindMember("path"); - if (path_itr == itr->GetObject().MemberEnd()) { - throw parser_exception( - remote_config_parser_result::target_files_path_field_missing); - } - if (!path_itr->value.IsString()) { - throw parser_exception(remote_config_parser_result:: - target_files_path_field_invalid_type); - } - - // Raw checks - rapidjson::Value::ConstMemberIterator const raw_itr = - itr->GetObject().FindMember("raw"); - if (raw_itr == itr->GetObject().MemberEnd()) { - throw parser_exception( - remote_config_parser_result::target_files_raw_field_missing); - } - if (!raw_itr->value.IsString()) { - throw parser_exception(remote_config_parser_result:: - target_files_raw_field_invalid_type); - } - result.insert({path_itr->value.GetString(), - {path_itr->value.GetString(), raw_itr->value.GetString()}}); - } - - return result; -} - -std::vector parse_client_configs( - rapidjson::Value::ConstMemberIterator client_configs_itr) -{ - std::vector result; - - for (rapidjson::Value::ConstValueIterator itr = - client_configs_itr->value.Begin(); - itr != client_configs_itr->value.End(); ++itr) { - if (!itr->IsString()) { - throw parser_exception( - remote_config_parser_result::client_config_field_invalid_entry); - } - - result.emplace_back(itr->GetString()); - } - - return result; -} - -std::pair parse_target( - rapidjson::Value::ConstMemberIterator target_itr) -{ - rapidjson::Value::ConstMemberIterator custom_itr; - validate_field_is_present(target_itr, "custom", rapidjson::kObjectType, - custom_itr, - remote_config_parser_result::custom_path_targets_field_missing, - remote_config_parser_result::custom_path_targets_field_invalid); - rapidjson::Value::ConstMemberIterator v_itr; - validate_field_is_present(custom_itr, "v", rapidjson::kNumberType, v_itr, - remote_config_parser_result::v_path_targets_field_missing, - remote_config_parser_result::v_path_targets_field_invalid); - - rapidjson::Value::ConstMemberIterator hashes_itr; - validate_field_is_present(target_itr, "hashes", rapidjson::kObjectType, - hashes_itr, - remote_config_parser_result::hashes_path_targets_field_missing, - remote_config_parser_result::hashes_path_targets_field_invalid); - - std::unordered_map hashes_mapped; - auto hashes_object = hashes_itr->value.GetObject(); - for (rapidjson::Value::ConstMemberIterator itr = - hashes_object.MemberBegin(); - itr != hashes_object.MemberEnd(); ++itr) { - if (itr->value.GetType() != rapidjson::kStringType) { - throw parser_exception(remote_config_parser_result:: - hash_hashes_path_targets_field_invalid); - } - - std::pair const hash_pair( - itr->name.GetString(), itr->value.GetString()); - hashes_mapped.insert(hash_pair); - } - - if (hashes_mapped.empty()) { - throw parser_exception( - remote_config_parser_result::hashes_path_targets_field_empty); - } - - rapidjson::Value::ConstMemberIterator length_itr; - validate_field_is_present(target_itr, "length", rapidjson::kNumberType, - length_itr, - remote_config_parser_result::length_path_targets_field_missing, - remote_config_parser_result::length_path_targets_field_invalid); - - std::string const target_name(target_itr->name.GetString()); - path const path_object = { - v_itr->value.GetInt(), hashes_mapped, length_itr->value.GetInt()}; - - return {target_name, path_object}; -} - -targets parse_targets_signed( - rapidjson::Value::ConstMemberIterator targets_signed_itr) -{ - rapidjson::Value::ConstMemberIterator version_itr; - validate_field_is_present(targets_signed_itr, "version", - rapidjson::kNumberType, version_itr, - remote_config_parser_result::version_signed_targets_field_missing, - remote_config_parser_result::version_signed_targets_field_invalid); - - rapidjson::Value::ConstMemberIterator targets_itr; - validate_field_is_present(targets_signed_itr, "targets", - rapidjson::kObjectType, targets_itr, - remote_config_parser_result::targets_signed_targets_field_missing, - remote_config_parser_result::targets_signed_targets_field_invalid); - - std::vector> paths; - for (rapidjson::Value::ConstMemberIterator current_target = - targets_itr->value.MemberBegin(); - current_target != targets_itr->value.MemberEnd(); ++current_target) { - auto path = parse_target(current_target); - paths.push_back(path); - } - - rapidjson::Value::ConstMemberIterator type_itr; - validate_field_is_present(targets_signed_itr, "_type", - rapidjson::kStringType, type_itr, - remote_config_parser_result::type_signed_targets_field_missing, - remote_config_parser_result::type_signed_targets_field_invalid); - if ("targets"sv != type_itr->value.GetString()) { - throw parser_exception(remote_config_parser_result:: - type_signed_targets_field_invalid_type); - } - - rapidjson::Value::ConstMemberIterator custom_itr; - validate_field_is_present(targets_signed_itr, "custom", - rapidjson::kObjectType, custom_itr, - remote_config_parser_result::custom_signed_targets_field_missing, - remote_config_parser_result::custom_signed_targets_field_invalid); - - rapidjson::Value::ConstMemberIterator opaque_backend_state_itr; - validate_field_is_present(custom_itr, "opaque_backend_state", - rapidjson::kStringType, opaque_backend_state_itr, - remote_config_parser_result::obs_custom_signed_targets_field_missing, - remote_config_parser_result::obs_custom_signed_targets_field_invalid); - std::unordered_map final_paths; - for (auto &[path_str, path] : paths) { - final_paths.emplace(path_str, path); - } - return {version_itr->value.GetInt(), - opaque_backend_state_itr->value.GetString(), final_paths}; -} - -targets parse_targets(rapidjson::Value::ConstMemberIterator targets_itr) -{ - std::string const targets_encoded_content = targets_itr->value.GetString(); - - if (targets_encoded_content.empty()) { - throw parser_exception( - remote_config_parser_result::targets_field_empty); - } - - std::string base64_decoded; - try { - base64_decoded = base64_decode(targets_encoded_content, true); - } catch (std::runtime_error &error) { - throw parser_exception( - remote_config_parser_result::targets_field_invalid_base64); - } - - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(base64_decoded).HasParseError()) { - throw parser_exception( - remote_config_parser_result::targets_field_invalid_json); - } - - rapidjson::Value::ConstMemberIterator signed_itr; - - // Lets validate the data and since we are there we get the iterators - validate_field_is_present(serialized_doc, "signed", rapidjson::kObjectType, - signed_itr, remote_config_parser_result::signed_targets_field_missing, - remote_config_parser_result::signed_targets_field_invalid); - - return parse_targets_signed(signed_itr); -} - -get_configs_response parse(const std::string &body) -{ - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(body).HasParseError()) { - throw parser_exception(remote_config_parser_result::invalid_json); - } - if (!serialized_doc.IsObject()) { - throw parser_exception(remote_config_parser_result::invalid_response); - } - - rapidjson::Value::ConstMemberIterator target_files_itr; - rapidjson::Value::ConstMemberIterator client_configs_itr; - rapidjson::Value::ConstMemberIterator targets_itr; - - // Lets validate the data and since we are here as we get the iterators - auto validated_target_files = validate_field_is_present(serialized_doc, - "target_files", rapidjson::kArrayType, target_files_itr, - remote_config_parser_result::allow_missing, - remote_config_parser_result::target_files_field_invalid_type); - - auto validated_client_configs = validate_field_is_present(serialized_doc, - "client_configs", rapidjson::kArrayType, client_configs_itr, - remote_config_parser_result::allow_missing, - remote_config_parser_result::client_config_field_invalid_type); - - auto validated_targets = validate_field_is_present(serialized_doc, - "targets", rapidjson::kStringType, targets_itr, - remote_config_parser_result::allow_missing, - remote_config_parser_result::targets_field_invalid_type); - - std::unordered_map target_files; - if (validated_target_files) { - target_files = parse_target_files(target_files_itr); - } - std::vector client_configs; - if (validated_client_configs) { - client_configs = parse_client_configs(client_configs_itr); - } - - std::optional targets; - if (validated_targets) { - targets = parse_targets(targets_itr); - } - - return {target_files, client_configs, targets}; -} - -info_response parse_info(const std::string &body) -{ - info_response response; - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(body).HasParseError()) { - throw parser_exception(remote_config_parser_result::invalid_json); - } - if (!serialized_doc.IsObject()) { - throw parser_exception(remote_config_parser_result::invalid_response); - } - - rapidjson::Value::ConstMemberIterator endpoints_itr; - validate_field_is_present(serialized_doc, "endpoints", - rapidjson::kArrayType, endpoints_itr, - remote_config_parser_result::endpoints_field_missing, - remote_config_parser_result::endpoints_field_invalid); - - for (rapidjson::Value::ConstValueIterator itr = - endpoints_itr->value.Begin(); - itr != endpoints_itr->value.End(); ++itr) { - if (itr->GetType() != rapidjson::kStringType) { - throw parser_exception( - remote_config_parser_result::invalid_endpoint); - } - - response.endpoints.emplace_back(itr->GetString()); - } - - return response; -} - -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define RESULT_AS_STR(entry) #entry, -namespace { -constexpr std::array - results_as_str = {PARSER_RESULTS(RESULT_AS_STR)}; -} // anonymous namespace -std::string_view remote_config_parser_result_to_str( - const remote_config_parser_result &result) -{ - if (result == remote_config_parser_result::num_of_values) { - return ""; - } - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - return results_as_str[(size_t)result]; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/parser.hpp b/appsec/src/helper/remote_config/protocol/tuf/parser.hpp deleted file mode 100644 index 4693199654..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/parser.hpp +++ /dev/null @@ -1,86 +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 "get_configs_response.hpp" -#include "info_response.hpp" - -namespace dds::remote_config::protocol { -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define PARSER_RESULTS(X) \ - X(success) \ - X(allow_missing) \ - X(invalid_json) \ - X(targets_field_empty) \ - X(targets_field_invalid_base64) \ - X(targets_field_invalid_json) \ - X(targets_field_invalid_type) \ - X(signed_targets_field_invalid) \ - X(signed_targets_field_missing) \ - X(type_signed_targets_field_invalid) \ - X(type_signed_targets_field_invalid_type) \ - X(type_signed_targets_field_missing) \ - X(version_signed_targets_field_invalid) \ - X(version_signed_targets_field_missing) \ - X(custom_signed_targets_field_invalid) \ - X(custom_signed_targets_field_missing) \ - X(obs_custom_signed_targets_field_invalid) \ - X(obs_custom_signed_targets_field_missing) \ - X(target_files_object_invalid) \ - X(target_files_field_invalid_type) \ - X(target_files_path_field_missing) \ - X(target_files_path_field_invalid_type) \ - X(target_files_raw_field_missing) \ - X(target_files_raw_field_invalid_type) \ - X(client_config_field_invalid_type) \ - X(client_config_field_invalid_entry) \ - X(targets_signed_targets_field_invalid) \ - X(targets_signed_targets_field_missing) \ - X(custom_path_targets_field_invalid) \ - X(custom_path_targets_field_missing) \ - X(v_path_targets_field_invalid) \ - X(v_path_targets_field_missing) \ - X(hashes_path_targets_field_invalid) \ - X(hashes_path_targets_field_missing) \ - X(hashes_path_targets_field_empty) \ - X(hash_hashes_path_targets_field_invalid) \ - X(length_path_targets_field_invalid) \ - X(length_path_targets_field_missing) \ - X(invalid_response) \ - X(endpoints_field_missing) \ - X(endpoints_field_invalid) \ - X(invalid_endpoint) - -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define RESULT_AS_ENUM_ENTRY(entry) entry, -enum class remote_config_parser_result : size_t { - PARSER_RESULTS(RESULT_AS_ENUM_ENTRY) num_of_values -}; - -std::string_view remote_config_parser_result_to_str( - const remote_config_parser_result &result); - -class parser_exception : public std::exception { -public: - explicit parser_exception(remote_config_parser_result error) - : message_(remote_config_parser_result_to_str(error)), error_(error) - {} - virtual const char *what() { return message_.c_str(); } - remote_config_parser_result get_error() { return error_; } - -protected: - std::string message_; - remote_config_parser_result error_; -}; - -get_configs_response parse(const std::string &body); -info_response parse_info(const std::string &body); - -} // namespace dds::remote_config::protocol \ No newline at end of file diff --git a/appsec/src/helper/remote_config/protocol/tuf/serializer.cpp b/appsec/src/helper/remote_config/protocol/tuf/serializer.cpp deleted file mode 100644 index ec69b5d3ae..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/serializer.cpp +++ /dev/null @@ -1,171 +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 "../../../json_helper.hpp" -#include "../cached_target_files.hpp" -#include "base64.h" -#include "exception.hpp" -#include "serializer.hpp" - -namespace dds::remote_config::protocol { - -void serialize_client_tracer(rapidjson::Document::AllocatorType &alloc, - rapidjson::Value &client_field, const client_tracer &client_tracer) -{ - rapidjson::Value tracer_object(rapidjson::kObjectType); - - tracer_object.AddMember("language", "php", alloc); - tracer_object.AddMember("runtime_id", client_tracer.runtime_id, alloc); - tracer_object.AddMember( - "tracer_version", client_tracer.tracer_version, alloc); - tracer_object.AddMember("service", client_tracer.service, alloc); - tracer_object.AddMember("env", client_tracer.env, alloc); - tracer_object.AddMember("app_version", client_tracer.app_version, alloc); - - rapidjson::Value extra_services_array(rapidjson::kArrayType); - - for (auto const &service_name : client_tracer.extra_services) { - rapidjson::Value service(rapidjson::kStringType); - service.SetString(service_name.c_str(), service_name.length(), alloc); - extra_services_array.PushBack(service, alloc); - } - - tracer_object.AddMember("extra_services", extra_services_array, alloc); - client_field.AddMember("client_tracer", tracer_object, alloc); -} - -void serialize_config_states(rapidjson::Document::AllocatorType &alloc, - rapidjson::Value &client_field, - const std::vector &config_states) -{ - rapidjson::Value config_states_object(rapidjson::kArrayType); - - for (const auto &config_state : config_states) { - rapidjson::Value config_state_object(rapidjson::kObjectType); - config_state_object.AddMember("id", config_state.id, alloc); - config_state_object.AddMember("version", config_state.version, alloc); - config_state_object.AddMember("product", config_state.product, alloc); - config_state_object.AddMember( - "apply_state", static_cast(config_state.apply_state), alloc); - config_state_object.AddMember( - "apply_error", config_state.apply_error, alloc); - config_states_object.PushBack(config_state_object, alloc); - } - - client_field.AddMember("config_states", config_states_object, alloc); -} - -void serialize_client_state(rapidjson::Document::AllocatorType &alloc, - rapidjson::Value &client_field, const client_state &client_state) -{ - rapidjson::Value client_state_object(rapidjson::kObjectType); - - client_state_object.AddMember( - "targets_version", client_state.targets_version, alloc); - client_state_object.AddMember("root_version", 1, alloc); - client_state_object.AddMember("has_error", client_state.has_error, alloc); - client_state_object.AddMember("error", client_state.error, alloc); - client_state_object.AddMember( - "backend_client_state", client_state.backend_client_state, alloc); - - serialize_config_states( - alloc, client_state_object, client_state.config_states); - - client_field.AddMember("state", client_state_object, alloc); -} - -void serialize_client(rapidjson::Document::AllocatorType &alloc, - rapidjson::Document &document, const client &client) -{ - rapidjson::Value client_object(rapidjson::kObjectType); - - client_object.AddMember("id", client.id, alloc); - client_object.AddMember("is_tracer", true, alloc); - - // NOLINTBEGIN - auto capabilities_int = - static_cast::type>( - client.capabilities); - char bytes[2] = {static_cast(capabilities_int >> 8), - static_cast(capabilities_int & 0x00FF)}; - - client_object.AddMember("capabilities", - base64_encode(std::string_view(bytes, 2), false), alloc); - // NOLINTEND - - rapidjson::Value products(rapidjson::kArrayType); - for (const std::string &product_str : client.products) { - products.PushBack(rapidjson::Value(product_str, alloc).Move(), alloc); - } - client_object.AddMember("products", products, alloc); - - serialize_client_tracer(alloc, client_object, client.client_tracer); - serialize_client_state(alloc, client_object, client.client_state); - - document.AddMember("client", client_object, alloc); -} - -void serialize_cached_target_files_hashes( - rapidjson::Document::AllocatorType &alloc, rapidjson::Value &parent, - const std::vector &cached_target_files_hash_list) -{ - rapidjson::Value cached_target_files_array(rapidjson::kArrayType); - - for (const cached_target_files_hash &ctfh : cached_target_files_hash_list) { - rapidjson::Value cached_target_file_hash_object(rapidjson::kObjectType); - cached_target_file_hash_object.AddMember( - "algorithm", ctfh.algorithm, alloc); - cached_target_file_hash_object.AddMember("hash", ctfh.hash, alloc); - cached_target_files_array.PushBack( - cached_target_file_hash_object, alloc); - } - - parent.AddMember("hashes", cached_target_files_array, alloc); -} - -void serialize_cached_target_files(rapidjson::Document::AllocatorType &alloc, - rapidjson::Document &document, - const std::vector &cached_target_files_list) -{ - rapidjson::Value cached_target_files_array(rapidjson::kArrayType); - - for (const cached_target_files &ctf : cached_target_files_list) { - rapidjson::Value cached_target_file_object(rapidjson::kObjectType); - cached_target_file_object.AddMember("path", ctf.path, alloc); - cached_target_file_object.AddMember("length", ctf.length, alloc); - serialize_cached_target_files_hashes( - alloc, cached_target_file_object, ctf.hashes); - cached_target_files_array.PushBack(cached_target_file_object, alloc); - } - - document.AddMember("cached_target_files", cached_target_files_array, alloc); -} - -std::string serialize(const get_configs_request &request) -{ - rapidjson::Document document; - rapidjson::Document::AllocatorType &alloc = document.GetAllocator(); - - document.SetObject(); - - serialize_client(alloc, document, request.client); - serialize_cached_target_files(alloc, document, request.cached_target_files); - - dds::string_buffer buffer; - rapidjson::Writer writer(buffer); - - // This has to be tested - if (!document.Accept(writer)) { - throw serializer_exception(); - } - - return buffer.get_string_ref(); -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/serializer.hpp b/appsec/src/helper/remote_config/protocol/tuf/serializer.hpp deleted file mode 100644 index 4e0bf009ef..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/serializer.hpp +++ /dev/null @@ -1,19 +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 "get_configs_request.hpp" - -namespace dds::remote_config::protocol { - -class serializer_exception : public std::exception {}; - -std::string serialize(const get_configs_request &request); - -} // namespace dds::remote_config::protocol \ No newline at end of file diff --git a/appsec/src/helper/remote_config/settings.hpp b/appsec/src/helper/remote_config/settings.hpp index 955ed14a5b..43a7b8340d 100644 --- a/appsec/src/helper/remote_config/settings.hpp +++ b/appsec/src/helper/remote_config/settings.hpp @@ -6,7 +6,7 @@ #pragma once -#include "utils.hpp" +#include "../utils.hpp" #include #include #include @@ -15,45 +15,31 @@ namespace dds::remote_config { -/* client_settings are currently the same for the whole client session. - * If this changes in the future, it will make sense to create a separation - * between 1) settings used for creating the engine and 2) settings used - * after, possibly when creating the subscriber listeners on every request - */ struct settings { - static constexpr uint32_t default_poll_interval{1000}; - static constexpr unsigned default_port{8126}; - // Remote config settings - bool enabled{false}; - std::string host; - unsigned port = default_port; - std::uint32_t poll_interval = default_poll_interval; - - // these two are specified in RCTE1 - // std::string targets_key; - // std::string targets_key_id; - // bool integrity_check_enabled{false}; - - MSGPACK_DEFINE_MAP(enabled, host, port, poll_interval); + bool enabled{}; + std::string shmem_path; bool operator==(const settings &oth) const noexcept { - return enabled == oth.enabled && host == oth.host && port == oth.port && - poll_interval == oth.poll_interval; + return enabled == oth.enabled && shmem_path == oth.shmem_path; } friend auto &operator<<(std::ostream &os, const settings &c) { return os << "{enabled=" << std::boolalpha << c.enabled - << ", host=" << c.host << ", port=" << c.port - << ", poll_interval=" << c.poll_interval << "}"; + << ", shmem_path=" << c.shmem_path << "}"; } - struct settings_hash { - std::size_t operator()(const settings &s) const noexcept - { - return hash(s.enabled, s.host, s.port, s.poll_interval); - } - }; + MSGPACK_DEFINE_MAP(enabled, shmem_path); }; } // namespace dds::remote_config + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::remote_config::settings &s) const noexcept + { + return dds::hash(s.enabled, s.shmem_path); + } +}; + +} // namespace std diff --git a/appsec/src/helper/runner.cpp b/appsec/src/helper/runner.cpp index 58322b6ded..dee62304fd 100644 --- a/appsec/src/helper/runner.cpp +++ b/appsec/src/helper/runner.cpp @@ -13,6 +13,22 @@ #include #include +extern "C" { +#include +} + +namespace { +struct ConfigInvariants; +struct Arc_Target; + +using in_proc_notify_fn = void (*)( + const ConfigInvariants *invariants, const Arc_Target *target); + +void (*ddog_set_rc_notify_fn)(in_proc_notify_fn notify_fn); +char *(*ddog_remote_config_path)(const ConfigInvariants *, const Arc_Target *); +void (*ddog_remote_config_path_free)(char *); +} // namespace + namespace dds { namespace { @@ -85,6 +101,54 @@ runner::runner(const config::config &cfg, } } +// NOLINTNEXTLINE +std::shared_ptr runner::RUNNER_FOR_NOTIFICATIONS{nullptr}; + +void runner::register_for_rc_notifications() +{ + SPDLOG_INFO("Register RC update callback"); + std::atomic_store(&runner::RUNNER_FOR_NOTIFICATIONS, shared_from_this()); + + ddog_set_rc_notify_fn( + [](const ConfigInvariants *invariants, const Arc_Target *target) { + char *path = ddog_remote_config_path(invariants, target); + + if (path == nullptr) { + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_ERROR("Failed to get remote config path"); + return; + } + + const std::shared_ptr runner = + std::atomic_load(&RUNNER_FOR_NOTIFICATIONS); + if (!runner) { + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_ERROR("No runner to notify of remote config updates"); + ddog_remote_config_path_free(path); + return; + } + + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_INFO("Remote config updated notification for {}", path); + // TODO: move the updates to a separate thread + runner->service_manager_->notify_of_rc_updates(path); + ddog_remote_config_path_free(path); + }); +} + +runner::~runner() noexcept +{ + try { + std::shared_ptr expected = shared_from_this(); + std::atomic_compare_exchange_strong(&RUNNER_FOR_NOTIFICATIONS, + &expected, std::shared_ptr(nullptr)); + } catch (...) { + // can only happened if there is no shared_ptr for the runner + // in this case a std::bad_weak_ptr is thrown + std::abort(); + } +} + void runner::run() { try { @@ -121,4 +185,31 @@ void runner::run() SPDLOG_INFO("Pool stopped"); } +void runner::resolve_symbols() +{ + // NOLINTNEXTLINE + ddog_set_rc_notify_fn = reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_set_rc_notify_fn")); + if (ddog_set_rc_notify_fn == nullptr) { + throw std::runtime_error{"Failed to resolve ddog_set_rc_notify_fn"}; + } + + ddog_remote_config_path = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_path")); + if (ddog_remote_config_path == nullptr) { + throw std::runtime_error{"Failed to resolve ddog_remote_config_path"}; + } + + ddog_remote_config_path_free = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_path_free")); + if (ddog_remote_config_path_free == nullptr) { + throw std::runtime_error{ + "Failed to resolve ddog_remote_config_path_free"}; + } +} + } // namespace dds diff --git a/appsec/src/helper/runner.hpp b/appsec/src/helper/runner.hpp index bde65b0edf..2c495b3910 100644 --- a/appsec/src/helper/runner.hpp +++ b/appsec/src/helper/runner.hpp @@ -17,7 +17,7 @@ namespace dds { -class runner { +class runner : public std::enable_shared_from_this { public: runner(const config::config &cfg, std::atomic &interrupted); runner(const config::config &cfg, network::base_acceptor::ptr &&acceptor, @@ -26,16 +26,22 @@ class runner { runner &operator=(const runner &) = delete; runner(runner &&) = delete; runner &operator=(runner &&) = delete; - ~runner() = default; + ~runner() noexcept; + + static void resolve_symbols(); void run() noexcept(false); + void register_for_rc_notifications(); + [[nodiscard]] bool interrupted() const { return interrupted_.load(std::memory_order_acquire); } private: + static std::shared_ptr RUNNER_FOR_NOTIFICATIONS; + const config::config &cfg_; // NOLINT std::shared_ptr service_manager_; worker::pool worker_pool_; diff --git a/appsec/src/helper/service.cpp b/appsec/src/helper/service.cpp index 561675deb3..bedb5e1e2d 100644 --- a/appsec/src/helper/service.cpp +++ b/appsec/src/helper/service.cpp @@ -10,20 +10,17 @@ namespace dds { service::service(std::shared_ptr engine, std::shared_ptr service_config, - dds::remote_config::client_handler::ptr &&client_handler, + std::unique_ptr &&client_handler, + std::string rc_path, const schema_extraction_settings &schema_extraction_settings) - : engine_(std::move(engine)), service_config_(std::move(service_config)), - client_handler_(std::move(client_handler)) + : engine_{std::move(engine)}, service_config_{std::move(service_config)}, + client_handler_{std::move(client_handler)}, rc_path_{std::move(rc_path)} { // The engine should always be valid if (!engine_) { throw std::runtime_error("invalid engine"); } - if (client_handler_) { - client_handler_->start(); - } - double sample_rate = schema_extraction_settings.sample_rate; if (!schema_extraction_settings.enabled) { @@ -31,23 +28,29 @@ service::service(std::shared_ptr engine, } schema_sampler_ = std::make_shared(sample_rate); + + if (client_handler_) { + client_handler_->poll(); + } } -service::ptr service::from_settings(service_identifier &&id, +std::shared_ptr service::from_settings( const dds::engine_settings &eng_settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement) { - auto engine_ptr = engine::from_settings(eng_settings, meta, metrics); + const std::shared_ptr engine_ptr = + engine::from_settings(eng_settings, meta, metrics); auto service_config = std::make_shared(); - auto client_handler = remote_config::client_handler::from_settings( - std::move(id), eng_settings, service_config, rc_settings, engine_ptr, - dynamic_enablement); + auto client_handler = + remote_config::client_handler::from_settings(eng_settings, + service_config, rc_settings, engine_ptr, dynamic_enablement); return std::make_shared(engine_ptr, std::move(service_config), - std::move(client_handler), eng_settings.schema_extraction); + std::move(client_handler), rc_settings.shmem_path, + eng_settings.schema_extraction); } } // namespace dds diff --git a/appsec/src/helper/service.hpp b/appsec/src/helper/service.hpp index 72e3a8a5dd..233926367d 100644 --- a/appsec/src/helper/service.hpp +++ b/appsec/src/helper/service.hpp @@ -10,7 +10,6 @@ #include "remote_config/client_handler.hpp" #include "sampler.hpp" #include "service_config.hpp" -#include "service_identifier.hpp" #include "std_logging.hpp" #include "utils.hpp" #include @@ -23,11 +22,10 @@ using namespace std::chrono_literals; class service { public: - using ptr = std::shared_ptr; - service(std::shared_ptr engine, std::shared_ptr service_config, - dds::remote_config::client_handler::ptr &&client_handler, + std::unique_ptr &&client_handler, + std::string rc_path, const schema_extraction_settings &schema_extraction_settings = {}); service(const service &) = delete; @@ -38,26 +36,12 @@ class service { virtual ~service() = default; - static service::ptr from_settings(service_identifier &&id, + static std::shared_ptr from_settings( const dds::engine_settings &eng_settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement); - virtual void register_runtime_id(const std::string &id) - { - if (client_handler_) { - client_handler_->register_runtime_id(id); - } - } - - virtual void unregister_runtime_id(const std::string &id) - { - if (client_handler_) { - client_handler_->unregister_runtime_id(id); - } - } - [[nodiscard]] std::shared_ptr get_engine() const { // TODO make access atomic? @@ -75,11 +59,19 @@ class service { return schema_sampler_; } + [[nodiscard]] bool is_remote_config_shmem_path(std::string_view path) + { + return rc_path_ == path; + } + + void notify_of_rc_updates() { client_handler_->poll(); } + protected: std::shared_ptr engine_{}; std::shared_ptr service_config_{}; - dds::remote_config::client_handler::ptr client_handler_{}; + std::unique_ptr client_handler_{}; std::shared_ptr schema_sampler_; + std::string rc_path_; }; } // namespace dds diff --git a/appsec/src/helper/service_identifier.hpp b/appsec/src/helper/service_identifier.hpp deleted file mode 100644 index fb967cf820..0000000000 --- a/appsec/src/helper/service_identifier.hpp +++ /dev/null @@ -1,55 +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 "utils.hpp" -#include -#include - -namespace dds { -struct service_identifier { - std::string service; - std::vector extra_services; - std::string env; - std::string tracer_version; - std::string app_version; - std::string runtime_id; - - MSGPACK_DEFINE_MAP( - service, extra_services, env, tracer_version, app_version, runtime_id); - - bool operator==(const service_identifier &oth) const noexcept - { - return service == oth.service && env == oth.env; - } - - friend auto &operator<<(std::ostream &os, const service_identifier &id) - { - os << "{service=" << id.service << ", env=" << id.env - << ", tracer_version=" << id.tracer_version - << ", app_version=" << id.app_version - << ", runtime_id=" << id.runtime_id; - - os << ", extra_services=["; - for (int i = 0; i < id.extra_services.size(); i++) { - os << id.extra_services[i]; - if (i + 1 < id.extra_services.size()) { - os << ", "; - } - } - os << "]}"; - - return os; - } - - struct hash { - std::size_t operator()(const service_identifier &id) const noexcept - { - return dds::hash(id.service, id.env); - } - }; -}; -} // namespace dds diff --git a/appsec/src/helper/service_manager.cpp b/appsec/src/helper/service_manager.cpp index 6bcf70b2d3..d4c058265d 100644 --- a/appsec/src/helper/service_manager.cpp +++ b/appsec/src/helper/service_manager.cpp @@ -8,28 +8,31 @@ namespace dds { std::shared_ptr service_manager::create_service( - service_identifier &&id, const engine_settings &settings, - const remote_config::settings &rc_settings, + const engine_settings &settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement) { - const std::lock_guard guard{mutex_}; + const cache_key key{settings, rc_settings}; - auto hit = cache_.find(id); + const std::lock_guard guard{mutex_}; + auto hit = cache_.find(key); if (hit != cache_.end()) { auto service_ptr = hit->second.lock(); if (service_ptr) { // not expired SPDLOG_DEBUG( - "Found an existing service for {}::{}", id.service, id.env); + "Found an existing service for settings={} rc_settings={}", + settings, rc_settings); return service_ptr; } } - SPDLOG_DEBUG("Creating a service for {}::{}", id.service, id.env); + SPDLOG_DEBUG("Creating a service for settings={} rc_settings={}", settings, + rc_settings); + + auto service_ptr = service::from_settings( + settings, rc_settings, meta, metrics, dynamic_enablement); + cache_.emplace(key, std::move(service_ptr)); - auto service_ptr = service::from_settings(service_identifier(id), settings, - rc_settings, meta, metrics, dynamic_enablement); - cache_.emplace(std::move(id), std::move(service_ptr)); last_service_ = service_ptr; cleanup_cache(); @@ -37,6 +40,27 @@ std::shared_ptr service_manager::create_service( return service_ptr; } +void service_manager::notify_of_rc_updates(std::string_view shmem_path) +{ + std::vector> services_to_notify; + { + const std::lock_guard guard{mutex_}; + for (auto &[key, service_ptr] : cache_) { + if (key.get_shmem_path() == shmem_path) { + if (std::shared_ptr service = service_ptr.lock()) { + services_to_notify.emplace_back(std::move(service)); + } + } + } + } // release lock + + SPDLOG_DEBUG( + "Notifying {} services of RC updates", services_to_notify.size()); + for (auto &service : services_to_notify) { + service->notify_of_rc_updates(); + } +} + void service_manager::cleanup_cache() { for (auto it = cache_.begin(); it != cache_.end();) { diff --git a/appsec/src/helper/service_manager.hpp b/appsec/src/helper/service_manager.hpp index ce0561b282..d4cb7a2186 100644 --- a/appsec/src/helper/service_manager.hpp +++ b/appsec/src/helper/service_manager.hpp @@ -6,6 +6,7 @@ #pragma once #include "engine.hpp" +#include "engine_settings.hpp" #include "exception.hpp" #include "network/proto.hpp" #include "service.hpp" @@ -21,25 +22,63 @@ namespace dds { class service_manager { public: - virtual ~service_manager() = default; service_manager() = default; + service_manager(const service_manager &) = delete; + service_manager &operator=(const service_manager &) = delete; + service_manager(service_manager &&) = delete; + service_manager &operator=(service_manager &&) = delete; + virtual ~service_manager() = default; - virtual std::shared_ptr create_service(service_identifier &&id, + virtual std::shared_ptr create_service( const engine_settings &settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement); + void notify_of_rc_updates(std::string_view shmem_path); + protected: - using cache_t = std::unordered_map, service_identifier::hash>; + class cache_key { + public: + cache_key(engine_settings engine_settings, + remote_config::settings config_settings) + : engine_settings_{std::move(engine_settings)}, + config_settings_{std::move(config_settings)}, + hash_{dds::hash(engine_settings_, config_settings_)} + {} + + bool operator==(const cache_key &other) const + { + return engine_settings_ == other.engine_settings_ && + config_settings_ == other.config_settings_; + } + + struct hash { + std::size_t operator()(const cache_key &key) const + { + return key.hash_; + } + }; + + [[nodiscard]] const std::string &get_shmem_path() const + { + return config_settings_.shmem_path; + } + + private: + engine_settings engine_settings_; + remote_config::settings config_settings_; + std::size_t hash_; + }; + + using cache_t = + std::unordered_map, cache_key::hash>; void cleanup_cache(); // mutex_ must be held when calling this - // TODO this should be some sort of time-based LRU cache - service::ptr last_service_; std::mutex mutex_; cache_t cache_; + std::shared_ptr last_service_; // keep always one }; } // namespace dds diff --git a/appsec/src/helper/subscriber/base.hpp b/appsec/src/helper/subscriber/base.hpp index 1ae415b4a9..addfc6752c 100644 --- a/appsec/src/helper/subscriber/base.hpp +++ b/appsec/src/helper/subscriber/base.hpp @@ -17,12 +17,8 @@ namespace dds { class subscriber { public: - using ptr = std::shared_ptr; - class listener { public: - using ptr = std::shared_ptr; - listener() = default; listener(const listener &) = default; listener &operator=(const listener &) = delete; @@ -49,8 +45,8 @@ class subscriber { virtual std::string_view get_name() = 0; virtual std::unordered_set get_subscriptions() = 0; - virtual listener::ptr get_listener() = 0; - virtual subscriber::ptr update(parameter &rule, + virtual std::unique_ptr get_listener() = 0; + virtual std::unique_ptr update(parameter &rule, std::map &meta, std::map &metrics) = 0; }; diff --git a/appsec/src/helper/subscriber/waf.cpp b/appsec/src/helper/subscriber/waf.cpp index 03970a1920..4a5062c006 100644 --- a/appsec/src/helper/subscriber/waf.cpp +++ b/appsec/src/helper/subscriber/waf.cpp @@ -15,13 +15,14 @@ #include #include +#include "../compression.hpp" #include "../json_helper.hpp" #include "../std_logging.hpp" #include "../tags.hpp" -#include "base64.h" -#include "compression.hpp" -#include "ddwaf.h" +#include "base.hpp" #include "waf.hpp" +#include +#include namespace dds::waf { @@ -337,10 +338,10 @@ instance::~instance() } } -instance::listener::ptr instance::get_listener() +std::unique_ptr instance::get_listener() { - return listener::ptr(new listener( - ddwaf_context_init(handle_), waf_timeout_, ruleset_version_)); + return std::make_unique( + ddwaf_context_init(handle_), waf_timeout_, ruleset_version_); } instance::instance( @@ -355,7 +356,7 @@ instance::instance( for (uint32_t i = 0; i < size; i++) { addresses_.emplace(addrs[i]); } } -subscriber::ptr instance::update(parameter &rule, +std::unique_ptr instance::update(parameter &rule, std::map &meta, std::map &metrics) { @@ -376,28 +377,29 @@ subscriber::ptr instance::update(parameter &rule, throw invalid_object(); } - return subscriber::ptr( + return std::unique_ptr( new instance(new_handle, waf_timeout_, std::move(version))); } -instance::ptr instance::from_settings(const engine_settings &settings, - const engine_ruleset &ruleset, std::map &meta, +std::unique_ptr instance::from_settings( + const engine_settings &settings, const engine_ruleset &ruleset, + std::map &meta, std::map &metrics) { dds::parameter param = json_to_parameter(ruleset.get_document()); - return std::make_shared(param, meta, metrics, + return std::make_unique(param, meta, metrics, settings.waf_timeout_us, settings.obfuscator_key_regex, settings.obfuscator_value_regex); } -instance::ptr instance::from_string(std::string_view rule, +std::unique_ptr instance::from_string(std::string_view rule, std::map &meta, std::map &metrics, std::uint64_t waf_timeout_us, std::string_view key_regex, std::string_view value_regex) { engine_ruleset const ruleset{rule}; dds::parameter param = json_to_parameter(ruleset.get_document()); - return std::make_shared( + return std::make_unique( param, meta, metrics, waf_timeout_us, key_regex, value_regex); } diff --git a/appsec/src/helper/subscriber/waf.hpp b/appsec/src/helper/subscriber/waf.hpp index cafc5922df..513855ddf6 100644 --- a/appsec/src/helper/subscriber/waf.hpp +++ b/appsec/src/helper/subscriber/waf.hpp @@ -24,7 +24,7 @@ class instance : public dds::subscriber { static constexpr int default_waf_timeout_us = 10000; static constexpr int max_plain_schema_allowed = 260; static constexpr int max_schema_size = 25000; - using ptr = std::shared_ptr; + class listener : public dds::subscriber::listener { public: listener(ddwaf_context ctx, std::chrono::microseconds waf_timeout, @@ -68,18 +68,19 @@ class instance : public dds::subscriber { return addresses_; } - listener::ptr get_listener() override; + std::unique_ptr get_listener() override; - subscriber::ptr update(parameter &rule, + std::unique_ptr update(parameter &rule, std::map &meta, std::map &metrics) override; - static instance::ptr from_settings(const engine_settings &settings, - const engine_ruleset &ruleset, std::map &meta, + static std::unique_ptr from_settings( + const engine_settings &settings, const engine_ruleset &ruleset, + std::map &meta, std::map &metrics); // testing only - static instance::ptr from_string(std::string_view rule, + static std::unique_ptr from_string(std::string_view rule, std::map &meta, std::map &metrics, std::uint64_t waf_timeout_us = default_waf_timeout_us, diff --git a/appsec/src/helper/utils.hpp b/appsec/src/helper/utils.hpp index ad657beae0..5d07ae5544 100644 --- a/appsec/src/helper/utils.hpp +++ b/appsec/src/helper/utils.hpp @@ -52,4 +52,22 @@ inline std::string dd_tolower(std::string string) std::string read_file(std::string_view filename); +#ifdef __linux__ +extern "C" int __xpg_strerror_r(int, char *, size_t); +#endif +inline std::string strerror_ts(int errnum) +{ + std::string buf(256, '\0'); // NOLINT + +#ifdef __linux__ + (void)__xpg_strerror_r(errnum, buf.data(), buf.size()); +#else + (void)strerror_r(errnum, buf.data(), buf.size()); +#endif + + buf.resize(std::strlen(buf.data())); + + return buf; +} + } // namespace dds diff --git a/appsec/tests/extension/report_backtrace_01.phpt b/appsec/tests/extension/report_backtrace_01.phpt index f4624e3263..6ec72ba86a 100644 --- a/appsec/tests/extension/report_backtrace_01.phpt +++ b/appsec/tests/extension/report_backtrace_01.phpt @@ -67,4 +67,4 @@ array(1) { } } } -} \ No newline at end of file +} diff --git a/appsec/tests/extension/rinit_agent_host_port_01.phpt b/appsec/tests/extension/rinit_agent_host_port_01.phpt deleted file mode 100644 index 2d4d297c62..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_01.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Agent host and port are taken from ENV ---ENV-- -DD_AGENT_HOST=1.2.3.4 -DD_TRACE_AGENT_PORT=567 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); - -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_02.phpt b/appsec/tests/extension/rinit_agent_host_port_02.phpt deleted file mode 100644 index 963b8bd136..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_02.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Fallback to default port if given not valid ---ENV-- -DD_TRACE_AGENT_PORT=99999 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["port"]); - -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -int(8126) diff --git a/appsec/tests/extension/rinit_agent_host_port_03.phpt b/appsec/tests/extension/rinit_agent_host_port_03.phpt deleted file mode 100644 index 4390878fa2..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_03.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Fallback to default port if given not valid ---ENV-- -DD_TRACE_AGENT_PORT=0 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["port"]); - -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -int(8126) diff --git a/appsec/tests/extension/rinit_agent_host_port_04.phpt b/appsec/tests/extension/rinit_agent_host_port_04.phpt deleted file mode 100644 index 38623573bf..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_04.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Agent host and port are taken from INI ---INI-- -datadog.agent_host=1.2.3.4 -datadog.trace.agent_port=567 ---ENV-- -DD_TRACE_AGENT_PORT= -DD_AGENT_HOST= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_05.phpt b/appsec/tests/extension/rinit_agent_host_port_05.phpt deleted file mode 100644 index a00a4d6b07..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_05.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Agent host and port can be taken from agent url on INI ---INI-- -datadog.trace.agent_url=http://1.2.3.4:567 ---ENV-- -DD_AGENT_HOST= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_06.phpt b/appsec/tests/extension/rinit_agent_host_port_06.phpt deleted file mode 100644 index 3e0f33105a..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_06.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Agent host and port can be taken from agent url on ENV ---ENV-- -DD_TRACE_AGENT_URL=http://1.2.3.4:567 -DD_AGENT_HOST= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_07.phpt b/appsec/tests/extension/rinit_agent_host_port_07.phpt deleted file mode 100644 index 51825dc6a7..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_07.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Agent host and port fallback to default when agent url ones are not valid ---ENV-- -DD_TRACE_AGENT_URL=http://:99999 -DD_TRACE_AGENT_PORT= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "127.0.0.1" -int(8126) diff --git a/appsec/tests/extension/rinit_agent_host_port_08.phpt b/appsec/tests/extension/rinit_agent_host_port_08.phpt deleted file mode 100644 index 7b1876d3ee..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_08.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -DD_AGENT_HOST and DD_TRACE_AGENT_PORT take priority over DD_TRACE_AGENT_URL ---ENV-- -DD_TRACE_AGENT_URL=http://2.2.2.2:1234 -DD_AGENT_HOST=1.2.3.4 -DD_TRACE_AGENT_PORT=567 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_rshutdown_basic.phpt b/appsec/tests/extension/rinit_rshutdown_basic.phpt index e26443a5675640653c7429393229d99b30037766..3a85e3a3c73d9c0c6396ea6c7f5557227b84d159 100644 GIT binary patch delta 49 zcmdm`-K@4jf^BmShZ56dO%CVD-JDw7(Zw0Lsk!k5i6t461Nr18=kp6so-fL`c?Gu+ F7XXb750?M{ delta 279 zcmZox+oiohf{i<|pdh|1wWv5VKW}mztIXsB{G1%Ad1bK*wss1WCvwX1q*jy^CB_%0 z7L{ctPrk?nX|d`1vyu=>$O%=HX_&=6#ZXhD8XPJCuw3D7Z#IS_dRgUN+_a{SQ+`9)A2 R7KTP, create_service, - (dds::service_identifier && id, const dds::engine_settings &settings, + (const dds::engine_settings &settings, const dds::remote_config::settings &rc_settings, (std::map & meta), (std::map & metrics), @@ -42,12 +42,8 @@ class service : public dds::service { public: service(std::shared_ptr engine, std::shared_ptr service_config) - : dds::service(engine, service_config, {}) + : dds::service(engine, service_config, {}, "/rc_path") {} - - MOCK_METHOD(void, register_runtime_id, (const std::string &id), (override)); - MOCK_METHOD( - void, unregister_runtime_id, (const std::string &id), (override)); }; } // namespace mock @@ -187,7 +183,6 @@ TEST(ClientTest, ClientInitRegisterRuntimeId) msg.pid = 1729; msg.runtime_version = "1.0"; msg.client_version = "2.0"; - msg.service.runtime_id = "thisisaruntimeid"; msg.engine_settings.rules_file = fn; network::request req(std::move(msg)); @@ -198,17 +193,11 @@ TEST(ClientTest, ClientInitRegisterRuntimeId) send(testing::An &>())) .WillOnce(DoAll(testing::SaveArg<0>(&res), Return(true))); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, true)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, true)) .Times(1) .WillOnce(Return(service)); - std::string runtime_id; - EXPECT_CALL(*service, register_runtime_id(_)) - .Times(1) - .WillOnce(testing::SaveArg<0>(&runtime_id)); - EXPECT_TRUE(c.run_client_init()); - EXPECT_STREQ(runtime_id.c_str(), "thisisaruntimeid"); } TEST(ClientTest, ClientInitGeneratesRuntimeId) @@ -238,17 +227,11 @@ TEST(ClientTest, ClientInitGeneratesRuntimeId) send(testing::An &>())) .WillOnce(DoAll(testing::SaveArg<0>(&res), Return(true))); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, true)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, true)) .Times(1) .WillOnce(Return(service)); - std::string runtime_id; - EXPECT_CALL(*service, register_runtime_id(_)) - .Times(1) - .WillOnce(testing::SaveArg<0>(&runtime_id)); - EXPECT_TRUE(c.run_client_init()); - EXPECT_STRNE(runtime_id.c_str(), ""); } TEST(ClientTest, ClientInitInvalidRules) @@ -1847,7 +1830,7 @@ TEST(ClientTest, ServiceIsCreatedDependingOnEnabledConfigurationValue) testing::An &>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, true)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, true)) .Times(1) .WillOnce(Return(service)); client c(smanager, std::unique_ptr(broker)); @@ -1863,7 +1846,7 @@ TEST(ClientTest, ServiceIsCreatedDependingOnEnabledConfigurationValue) send( testing::An &>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, false)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, false)) .Times(1) .WillOnce(Return(service)); client c(smanager, std::unique_ptr(broker)); @@ -1879,7 +1862,7 @@ TEST(ClientTest, ServiceIsCreatedDependingOnEnabledConfigurationValue) send( testing::An &>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, false)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, false)) .Times(1) .WillOnce(Return(service)); client c(smanager, std::unique_ptr(broker)); diff --git a/appsec/tests/helper/engine_test.cpp b/appsec/tests/helper/engine_test.cpp index eaee4d231b..1778e17337 100644 --- a/appsec/tests/helper/engine_test.cpp +++ b/appsec/tests/helper/engine_test.cpp @@ -6,6 +6,7 @@ #include "common.hpp" #include "json_helper.hpp" #include +#include #include #include @@ -19,8 +20,6 @@ namespace dds { namespace mock { class listener : public dds::subscriber::listener { public: - typedef std::shared_ptr ptr; - MOCK_METHOD2(call, void(dds::parameter_view &, dds::event &)); MOCK_METHOD2( get_meta_and_metrics, void(std::map &, @@ -29,12 +28,10 @@ class listener : public dds::subscriber::listener { class subscriber : public dds::subscriber { public: - typedef std::shared_ptr ptr; - MOCK_METHOD0(get_name, std::string_view()); - MOCK_METHOD0(get_listener, dds::subscriber::listener::ptr()); + MOCK_METHOD0(get_listener, std::unique_ptr()); MOCK_METHOD0(get_subscriptions, std::unordered_set()); - MOCK_METHOD3(update, dds::subscriber::ptr(dds::parameter &, + MOCK_METHOD3(update, std::unique_ptr(dds::parameter &, std::map &meta, std::map &metrics)); }; @@ -55,17 +52,18 @@ TEST(EngineTest, SingleSubscriptor) { auto e{engine::create()}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*listener, call(_, _)) - .WillRepeatedly( - Invoke([](dds::parameter_view &data, dds::event &event_) -> void { - event_.actions.push_back({dds::action_type::block, {}}); - })); - - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Invoke([]() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)) + .WillRepeatedly(Invoke( + [](dds::parameter_view &data, dds::event &event_) -> void { + event_.actions.push_back({dds::action_type::block, {}}); + })); + return listener; + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -87,7 +85,8 @@ using namespace std::literals; TEST(EngineTest, MultipleSubscriptors) { auto e{engine::create()}; - mock::listener::ptr blocker = mock::listener::ptr(new mock::listener()); + + auto blocker = std::make_unique(); EXPECT_CALL(*blocker, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -98,7 +97,7 @@ TEST(EngineTest, MultipleSubscriptors) } })); - mock::listener::ptr recorder = mock::listener::ptr(new mock::listener()); + auto recorder = std::make_unique(); EXPECT_CALL(*recorder, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -108,21 +107,31 @@ TEST(EngineTest, MultipleSubscriptors) } })); - mock::listener::ptr ignorer = mock::listener::ptr(new mock::listener()); + std::unique_ptr ignorer = + std::unique_ptr(new mock::listener()); EXPECT_CALL(*ignorer, call(_, _)).Times(testing::AnyNumber()); - mock::subscriber::ptr sub1 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub1, get_listener()).WillRepeatedly(Return(blocker)); + std::unique_ptr sub1 = + std::unique_ptr(new mock::subscriber()); + EXPECT_CALL(*sub1, get_listener()).WillRepeatedly(Invoke([&]() { + return std::move(blocker); + })); - mock::subscriber::ptr sub2 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub2, get_listener()).WillRepeatedly(Return(recorder)); + std::unique_ptr sub2 = + std::unique_ptr(new mock::subscriber()); + EXPECT_CALL(*sub2, get_listener()).WillRepeatedly(Invoke([&]() { + return std::move(recorder); + })); - mock::subscriber::ptr sub3 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub3, get_listener()).WillRepeatedly(Return(ignorer)); + std::unique_ptr sub3 = + std::unique_ptr(new mock::subscriber()); + EXPECT_CALL(*sub3, get_listener()).WillRepeatedly(Invoke([&]() { + return std::move(ignorer); + })); - e->subscribe(sub1); - e->subscribe(sub2); - e->subscribe(sub3); + e->subscribe(std::move(sub1)); + e->subscribe(std::move(sub2)); + e->subscribe(std::move(sub3)); auto ctx = e->get_context(); @@ -194,21 +203,23 @@ TEST(EngineTest, StatefulSubscriptor) auto e{engine::create()}; int attempt = 0; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*listener, call(_, _)) - .Times(6) - .WillRepeatedly(Invoke( - [&attempt](dds::parameter_view &data, dds::event &event_) -> void { + + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Invoke([&]() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)) + .Times(3) + .WillRepeatedly(Invoke([&attempt](dds::parameter_view &data, + dds::event &event_) -> void { if (attempt == 2 || attempt == 5) { event_.actions.push_back({dds::action_type::block, {}}); } attempt++; })); + return listener; + })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); - - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -251,7 +262,7 @@ TEST(EngineTest, WafDefaultActions) { auto e{engine::create(engine_settings::default_trace_rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); + auto listener = std::make_unique(); EXPECT_CALL(*listener, call(_, _)) .WillRepeatedly(Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -261,10 +272,12 @@ TEST(EngineTest, WafDefaultActions) event_.actions.push_back({dds::action_type::extract_schema, {}}); })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillOnce(Invoke([&]() { + return std::move(listener); + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -293,7 +306,7 @@ TEST(EngineTest, InvalidActionsAreDiscarded) { auto e{engine::create(engine_settings::default_trace_rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); + auto listener = std::make_unique(); EXPECT_CALL(*listener, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -301,10 +314,12 @@ TEST(EngineTest, InvalidActionsAreDiscarded) event_.actions.push_back({dds::action_type::block, {}}); })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillOnce(Invoke([&]() { + return std::move(listener); + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -330,8 +345,10 @@ TEST(EngineTest, WafSubscriptorBasic) auto e{engine::create()}; - auto waf_ptr = waf::instance::from_string(waf_rule, meta, metrics); - e->subscribe(waf_ptr); + auto waf_uniq_ptr = waf::instance::from_string(waf_rule, meta, metrics); + auto *waf_ptr = waf_uniq_ptr.get(); + + e->subscribe(std::move(waf_uniq_ptr)); EXPECT_STREQ(waf_ptr->get_name().data(), "waf"); @@ -390,27 +407,36 @@ TEST(EngineTest, MockSubscriptorsUpdateRuleData) { auto e{engine::create()}; - mock::listener::ptr ignorer = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*ignorer, call(_, _)).Times(testing::AnyNumber()); - - mock::subscriber::ptr new_sub1 = - mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*new_sub1, get_listener()).WillOnce(Return(ignorer)); - - mock::subscriber::ptr sub1 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub1, update(_, _, _)).WillOnce(Return(new_sub1)); + auto ignorer = []() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)).Times(testing::AnyNumber()); + return listener; + }; + + auto new_sub1 = std::make_unique(); + EXPECT_CALL(*new_sub1, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); + + auto sub1 = std::make_unique(); + EXPECT_CALL(*sub1, update(_, _, _)).WillOnce(Invoke([&]() { + return std::move(new_sub1); + })); EXPECT_CALL(*sub1, get_name()).WillRepeatedly(Return("")); - mock::subscriber::ptr new_sub2 = - mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*new_sub2, get_listener()).WillOnce(Return(ignorer)); + auto new_sub2 = std::make_unique(); + EXPECT_CALL(*new_sub2, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); - mock::subscriber::ptr sub2 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub2, update(_, _, _)).WillOnce(Return(new_sub2)); + auto sub2 = std::make_unique(); + EXPECT_CALL(*sub2, update(_, _, _)).WillOnce(Invoke([&]() { + return std::move(new_sub2); + })); EXPECT_CALL(*sub2, get_name()).WillRepeatedly(Return("")); - e->subscribe(sub1); - e->subscribe(sub2); + e->subscribe(std::move(sub1)); + e->subscribe(std::move(sub2)); std::map meta; std::map metrics; @@ -433,21 +459,28 @@ TEST(EngineTest, MockSubscriptorsInvalidRuleData) { auto e{engine::create()}; - mock::listener::ptr ignorer = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*ignorer, call(_, _)).Times(testing::AnyNumber()); + auto ignorer = []() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)).Times(testing::AnyNumber()); + return listener; + }; - mock::subscriber::ptr sub1 = mock::subscriber::ptr(new mock::subscriber()); + auto sub1 = std::make_unique(); EXPECT_CALL(*sub1, update(_, _, _)).WillRepeatedly(Throw(std::exception())); EXPECT_CALL(*sub1, get_name()).WillRepeatedly(Return("")); - EXPECT_CALL(*sub1, get_listener()).WillOnce(Return(ignorer)); + EXPECT_CALL(*sub1, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); - mock::subscriber::ptr sub2 = mock::subscriber::ptr(new mock::subscriber()); + auto sub2 = std::make_unique(); EXPECT_CALL(*sub2, update(_, _, _)).WillRepeatedly(Throw(std::exception())); EXPECT_CALL(*sub2, get_name()).WillRepeatedly(Return("")); - EXPECT_CALL(*sub2, get_listener()).WillOnce(Return(ignorer)); + EXPECT_CALL(*sub2, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); - e->subscribe(sub1); - e->subscribe(sub2); + e->subscribe(std::move(sub1)); + e->subscribe(std::move(sub2)); std::map meta; std::map metrics; @@ -857,17 +890,19 @@ TEST(EngineTest, RateLimiterForceKeep) int rate_limit = 0; auto e{engine::create(rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); + auto listener = std::make_unique(); EXPECT_CALL(*listener, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { event_.actions.push_back({dds::action_type::redirect, {}}); })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillOnce(Invoke([&]() { + return std::move(listener); + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); parameter p = parameter::map(); p.add("a", parameter::string("value"sv)); @@ -881,17 +916,18 @@ TEST(EngineTest, RateLimiterDoNotForceKeep) int rate_limit = 1; auto e{engine::create(rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*listener, call(_, _)) - .WillRepeatedly( - Invoke([](dds::parameter_view &data, dds::event &event_) -> void { - event_.actions.push_back({dds::action_type::redirect, {}}); - })); - - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); - - e->subscribe(sub); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Invoke([&]() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)) + .WillOnce(Invoke( + [](dds::parameter_view &data, dds::event &event_) -> void { + event_.actions.push_back({dds::action_type::redirect, {}}); + })); + return listener; + })); + + e->subscribe(std::move(sub)); parameter p = parameter::map(); p.add("a", parameter::string("value"sv)); diff --git a/appsec/tests/helper/remote_config/client_handler_test.cpp b/appsec/tests/helper/remote_config/client_handler_test.cpp deleted file mode 100644 index aefaf40f41..0000000000 --- a/appsec/tests/helper/remote_config/client_handler_test.cpp +++ /dev/null @@ -1,331 +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.hpp" -#include "mocks.hpp" -#include "remote_config/client_handler.hpp" - -namespace dds { - -namespace mock { - -class client_handler : public remote_config::client_handler { -public: - client_handler(remote_config::client::ptr &&rc_client, - std::shared_ptr service_config, - const std::chrono::milliseconds &poll_interval) - : remote_config::client_handler( - std::move(rc_client), service_config, poll_interval) - {} - void set_max_interval(std::chrono::milliseconds new_interval) - { - max_interval = new_interval; - } - auto get_max_interval() { return max_interval; } - const std::chrono::milliseconds get_current_interval() { return interval_; } - void tick() { remote_config::client_handler::tick(); } - - auto get_errors() { return errors_; } -}; - -} // namespace mock - -ACTION_P(SignalCall, promise) { promise->set_value(true); } - -class ClientHandlerTest : public ::testing::Test { -public: - service_identifier sid{"service", {"extra_service01", "extra_service02"}, - "env", "tracer_version", "app_version", "runtime_id"}; - dds::engine_settings settings; - remote_config::settings rc_settings; - std::shared_ptr service_config; - service_identifier id; - std::shared_ptr engine; - - void SetUp() - { - service_config = std::make_shared(); - id = sid; - engine = engine::create(); - rc_settings.enabled = true; - } -}; - -TEST_F(ClientHandlerTest, IfRemoteConfigDisabledItDoesNotGenerateHandler) -{ - rc_settings.enabled = false; - - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, false); - - EXPECT_FALSE(client_handler); -} - -TEST_F(ClientHandlerTest, IfNoServiceConfigProvidedItDoesNotGenerateHandler) -{ - std::shared_ptr null_service_config = {}; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, null_service_config, rc_settings, - engine, false); - - EXPECT_FALSE(client_handler); -} - -TEST_F(ClientHandlerTest, RuntimeIdIsNotGeneratedIfProvided) -{ - const char *runtime_id = "some runtime id"; - id.runtime_id = runtime_id; - - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, false); - - EXPECT_STREQ(runtime_id, client_handler->get_client() - ->get_service_identifier() - .runtime_id.c_str()); -} - -TEST_F(ClientHandlerTest, AsmFeatureProductIsAddeWhenDynamicEnablement) -{ - auto dynamic_enablement = true; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, dynamic_enablement); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_FEATURES") != products_list.end()); -} - -TEST_F( - ClientHandlerTest, AsmFeatureProductIsNotAddeWhenDynamicEnablementDisabled) -{ - auto dynamic_enablement = false; - - // Clear rules file so at least some other products are added - settings.rules_file.clear(); - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, dynamic_enablement); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_FEATURES") == products_list.end()); -} - -TEST_F(ClientHandlerTest, SomeProductsDependOnDynamicEngineBeingSet) -{ - { // When rules file is not set, products are added - settings.rules_file.clear(); - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, true); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_DATA") != products_list.end()); - EXPECT_TRUE(products_list.find("ASM_DD") != products_list.end()); - EXPECT_TRUE(products_list.find("ASM") != products_list.end()); - } - - { // When rules file is set, products not are added - settings.rules_file = "/some/file"; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, true); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_DATA") == products_list.end()); - EXPECT_TRUE(products_list.find("ASM_DD") == products_list.end()); - EXPECT_TRUE(products_list.find("ASM") == products_list.end()); - } -} - -TEST_F(ClientHandlerTest, IfNoProductsAreRequiredRemoteClientIsNotGenerated) -{ - settings.rules_file = "/some/file"; - auto dynamic_enablement = false; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, dynamic_enablement); - - EXPECT_FALSE(client_handler); -} - -TEST_F(ClientHandlerTest, ValidateRCThread) -{ - std::promise poll_call_promise; - auto poll_call_future = poll_call_promise.get_future(); - std::promise available_call_promise; - auto available_call_future = available_call_promise.get_future(); - - auto rc_client = std::make_unique(sid); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(1) - .WillOnce(DoAll(SignalCall(&available_call_promise), Return(true))); - EXPECT_CALL(*rc_client, poll) - .Times(1) - .WillOnce(DoAll(SignalCall(&poll_call_promise), Return(true))); - - auto client_handler = remote_config::client_handler( - std::move(rc_client), service_config, 200ms); - - client_handler.start(); - - // wait a little bit - this might end up being flaky - poll_call_future.wait_for(400ms); - available_call_future.wait_for(200ms); -} - -TEST_F(ClientHandlerTest, WhenRcNotAvailableItKeepsDiscovering) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(2) - .WillOnce(Return(false)) - .WillOnce(Return(false)); - EXPECT_CALL(*rc_client, poll).Times(0); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 500ms); - - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, WhenPollFailsItGoesBackToDiscovering) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(2) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - EXPECT_CALL(*rc_client, poll) - .Times(1) - .WillOnce(Throw(dds::remote_config::network_exception("some"))); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 500ms); - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, WhenDiscoverFailsItStaysOnDiscovering) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(3) - .WillOnce(Return(false)) - .WillOnce(Throw(dds::remote_config::network_exception("some"))) - .WillOnce(Throw(dds::remote_config::network_exception("some"))); - EXPECT_CALL(*rc_client, poll).Times(0); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 50ms); - client_handler.set_max_interval(100ms); - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, ItKeepsPollingWhileNoError) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(*rc_client, poll) - .Times(2) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 500ms); - - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, ItDoesNotStartIfNoRcClientGiven) -{ - auto rc_client = nullptr; - auto client_handler = - remote_config::client_handler(rc_client, service_config, 500ms); - - EXPECT_FALSE(client_handler.start()); -} - -TEST_F(ClientHandlerTest, ItDoesNotGoOverMaxIfGivenInitialIntervalIsLower) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(3) - .WillRepeatedly(Return(false)); - - auto max_interval = 300ms; - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 299ms); - client_handler.set_max_interval(max_interval); - - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); - - EXPECT_EQ(max_interval, client_handler.get_current_interval()); - EXPECT_EQ(3, client_handler.get_errors()); -} - -TEST_F(ClientHandlerTest, IfInitialIntervalIsHigherThanMaxItBecomesNewMax) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(3) - .WillRepeatedly(Return(false)); - - auto interval = 200ms; - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, interval); - client_handler.set_max_interval(100ms); - - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); - - EXPECT_EQ(interval, client_handler.get_current_interval()); - EXPECT_EQ(3, client_handler.get_errors()); -} - -TEST_F(ClientHandlerTest, ByDefaultMaxIntervalisFiveMinutes) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 200ms); - - EXPECT_EQ(5min, client_handler.get_max_interval()); -} - -TEST_F(ClientHandlerTest, RegisterAndUnregisterRuntimeID) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, register_runtime_id).Times(1); - EXPECT_CALL(*rc_client, unregister_runtime_id).Times(1); - - auto client_handler = remote_config::client_handler( - std::move(rc_client), service_config, 200ms); - - client_handler.register_runtime_id("something"); - client_handler.unregister_runtime_id("something"); -} - -} // namespace dds diff --git a/appsec/tests/helper/remote_config/client_test.cpp b/appsec/tests/helper/remote_config/client_test.cpp deleted file mode 100644 index 262960d9c6..0000000000 --- a/appsec/tests/helper/remote_config/client_test.cpp +++ /dev/null @@ -1,1348 +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 "../common.hpp" -#include "base64.h" -#include "json_helper.hpp" -#include "remote_config/client.hpp" -#include "remote_config/exception.hpp" -#include "remote_config/listeners/listener.hpp" -#include "remote_config/product.hpp" -#include "remote_config/protocol/client.hpp" -#include "remote_config/protocol/client_state.hpp" -#include "remote_config/protocol/client_tracer.hpp" -#include "remote_config/protocol/config_state.hpp" -#include "remote_config/protocol/tuf/get_configs_request.hpp" -#include "remote_config/protocol/tuf/serializer.hpp" -#include "service_identifier.hpp" -#include "spdlog/fmt/bundled/core.h" - -using capabilities_e = dds::remote_config::protocol::capabilities_e; - -namespace dds { -class dummy_listener : public remote_config::listener_base { -public: - explicit dummy_listener(std::string_view name_ = "MOCK_PRODUCT") - : name(name_) - {} - void on_update(const remote_config::config &config) override {} - void on_unapply(const remote_config::config &config) override {} - void init() override {} - void commit() override {} - - [[nodiscard]] std::unordered_map - get_supported_products() override - { - return { - {name, remote_config::protocol::capabilities_e::ASM_ACTIVATION}}; - } - - std::string name; -}; - -namespace mock { - -// The simple custom action -ACTION_P(set_response_body, response) { arg1.assign(response); } - -ACTION(ThrowErrorApplyingConfig) -{ - throw remote_config::error_applying_config("some error"); -} - -class api : public remote_config::http_api { -public: - api() : http_api("0.0.0.0", "1234"){}; - MOCK_METHOD(std::string, get_configs, (std::string && request), (const)); -}; - -class listener_mock : public remote_config::listener_base { -public: - listener_mock() = default; - listener_mock(std::string_view name_) : name(name_) {} - ~listener_mock() override = default; - MOCK_METHOD( - void, on_update, ((const remote_config::config &config)), (override)); - MOCK_METHOD( - void, on_unapply, ((const remote_config::config &config)), (override)); - [[nodiscard]] std::unordered_map - get_supported_products() override - { - return { - {name, remote_config::protocol::capabilities_e::ASM_ACTIVATION}}; - } - - MOCK_METHOD(void, init, (), (override)); - MOCK_METHOD(void, commit, (), (override)); - std::string name{"MOCK_PRODUCT"}; -}; -} // namespace mock - -namespace test_helpers { -std::string sha256_from_path(std::string path) { return path + "_sha256"; } - -std::string raw_from_path(std::string path) { return path + "_raw"; } - -// Just a deterministic way of asserting this and avoid hadcoding much -int version_from_path(std::string path) { return path.length() + 55; } - -int length_from_path(std::string path) { return path.length(); } -} // namespace test_helpers - -class test_client : public remote_config::client { -public: - test_client(std::string id, - std::unique_ptr &&arg_api, - service_identifier &&sid, remote_config::settings &&settings, - std::vector listeners = {}) - : remote_config::client(std::move(arg_api), std::move(sid), - std::move(settings), listeners) - { - id_ = std::move(id); - } -}; - -class RemoteConfigClient : public ::testing::Test { -public: - std::string id; - std::string runtime_id; - std::string tracer_version; - std::string service; - std::vector extra_services; - std::string env; - std::string app_version; - std::string backend_client_state; - int target_version; - std::string asm_features; - std::string asm_dd; - std::string apm_sampling; - std::vector products_str; - std::vector listeners_; - std::string first_product_product; - std::string first_product_id; - std::string second_product_product; - std::string second_product_id; - std::string first_path; - std::string second_path; - std::vector paths; - remote_config::settings settings; - remote_config::protocol::capabilities_e capabilities; - - void SetUp() - { - // Since most values are moved to the classes, they need to be generated - // again on each set up - id = "some id"; - runtime_id = "some runtime id"; - tracer_version = "some tracer version"; - service = "some service"; - extra_services = {"service01", "service02"}; - env = "some env"; - app_version = "some app version"; - backend_client_state = "some backend state here"; - target_version = 123; - asm_features = "ASM_FEATURES"; - asm_dd = "ASM_DD"; - apm_sampling = "APM_SAMPLING"; - products_str = {asm_dd, asm_features}; - - first_product_product = asm_features; - first_product_id = "2.test1.config"; - first_path = "employee/" + first_product_product + "/" + - first_product_id + "/config"; - second_product_product = asm_features; - second_product_id = "luke.steensen"; - second_path = "datadog/2/" + second_product_product + "/" + - second_product_id + "/config"; - paths = {first_path, second_path}; - capabilities = remote_config::protocol::capabilities_e::ASM_ACTIVATION; - generate_listeners(); - } - - void generate_listeners() - { - for (std::string_view p_str : products_str) { - listeners_.push_back(std::make_shared(p_str)); - } - } - - remote_config::protocol::client generate_client(bool generate_state) - { - remote_config::protocol::client_tracer client_tracer = {runtime_id, - tracer_version, service, extra_services, env, app_version}; - - std::vector config_states; - int _target_version; - std::string _backend_client_state; - if (generate_state) { - // All these states are extracted from the harcoded request/response - std::string product00(first_product_product); - std::string product00_id(first_product_id); - remote_config::protocol::config_state cs00 = {product00_id, - test_helpers::version_from_path(first_path), product00, - remote_config::protocol::config_state::applied_state:: - ACKNOWLEDGED, - ""}; - std::string product01(second_product_product); - std::string product01_id(second_product_id); - remote_config::protocol::config_state cs01 = {product01_id, - test_helpers::version_from_path(second_path), product01, - remote_config::protocol::config_state::applied_state:: - ACKNOWLEDGED, - ""}; - - config_states.push_back(cs00); - config_states.push_back(cs01); - _target_version = target_version; - // This field is extracted from the harcoded response - _backend_client_state = backend_client_state; - } else { - _target_version = 0; // Default target version - _backend_client_state = ""; - } - remote_config::protocol::client_state client_state = { - _target_version, config_states, false, "", _backend_client_state}; - - auto products_str_cpy = products_str; - auto id_cpy = id; - remote_config::protocol::client c = {id_cpy, products_str_cpy, - client_tracer, client_state, capabilities}; - - return c; - } - - std::string generate_targets( - std::vector paths, std::string opaque_backend_state) - { - std::string targets_str; - for (int i = 0; i < paths.size(); i++) { - std::string path = paths[i]; - std::string sha256 = test_helpers::sha256_from_path(path); - targets_str.append( - ("\"" + path + "\": {\"custom\": {\"v\": " + - std::to_string(test_helpers::version_from_path(path)) + - " }, \"hashes\": {\"sha256\": \"" + sha256 + - "\"}, \"length\": " + - std::to_string(test_helpers::length_from_path(paths[i])) + - " }")); - if (i + 1 < paths.size()) { - targets_str.append(","); - } - } - - std::string targets_json = - ("{\"signatures\": [], \"signed\": {\"_type\": \"targets\", " - "\"custom\": {\"opaque_backend_state\": \"" + - opaque_backend_state + - "\"}, " - "\"expires\": \"2022-11-04T13:31:59Z\", \"spec_version\": " - "\"1.0.0\", \"targets\": {" + - targets_str + - "}, \"version\": " + std::to_string(target_version) + " } }"); - - return base64_encode(targets_json); - } - - std::string generate_example_response( - std::vector client_configs, - std::vector target_files, - std::vector target_paths) - { - std::string client_configs_str = ""; - std::string target_files_str = ""; - for (int i = 0; i < client_configs.size(); i++) { - client_configs_str.append("\"" + client_configs[i] + "\""); - if (i + 1 < client_configs.size()) { - client_configs_str.append(", "); - } - } - for (int i = 0; i < target_files.size(); i++) { - target_files_str.append( - "{\"path\": \"" + target_files[i] + "\", \"raw\": \"" + - test_helpers::raw_from_path(target_files[i]) + "\"}"); - if (i + 1 < target_files.size()) { - target_files_str.append(","); - } - } - return ("{\"roots\": [], \"targets\": \"" + - generate_targets(target_paths, backend_client_state) + - "\", \"target_files\": [" + target_files_str + - "], " - "\"client_configs\": [" + - client_configs_str + - "] " - "}"); - } - - std::string generate_example_response(std::vector paths) - { - return generate_example_response(paths, paths, paths); - } - - remote_config::protocol::get_configs_request generate_request( - bool generate_state, bool generate_cache) - { - dds::remote_config::protocol::client protocol_client = - generate_client(generate_state); - std::vector files; - if (generate_cache) { - // First cached file - remote_config::protocol::cached_target_files_hash hash01{ - "sha256", test_helpers::sha256_from_path(paths[0])}; - std::string path01 = paths[0]; - remote_config::protocol::cached_target_files file01 = { - path01, test_helpers::length_from_path(path01), {hash01}}; - files.push_back(file01); - - // Second cached file - remote_config::protocol::cached_target_files_hash hash02{ - "sha256", test_helpers::sha256_from_path(paths[1])}; - std::string path02 = paths[1]; - remote_config::protocol::cached_target_files file02{ - path02, test_helpers::length_from_path(path02), {hash02}}; - files.push_back(file02); - } - return {std::move(protocol_client), std::move(files)}; - } - - std::string generate_request_serialized( - bool generate_state, bool generate_cache) - { - std::optional request_serialized; - - request_serialized = remote_config::protocol::serialize( - generate_request(generate_state, generate_cache)); - - return request_serialized.value(); - } - - bool validate_request_has_error( - std::string request_serialized, bool has_error, std::string error_msg) - { - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(request_serialized).HasParseError()) { - return false; - } - - rapidjson::Value::ConstMemberIterator state_itr = - serialized_doc.FindMember("client")->value.FindMember("state"); - - // Has error field - rapidjson::Value::ConstMemberIterator itr = - state_itr->value.FindMember("has_error"); - rapidjson::Type expected_type = - has_error ? rapidjson::kTrueType : rapidjson::kFalseType; - if (itr->value.GetType() != expected_type) { - return false; - } - - // Error field - itr = state_itr->value.FindMember("error"); - if (itr->value.GetType() != rapidjson::kStringType || - error_msg != itr->value.GetString()) { - return false; - } - - return true; - } -}; - -TEST_F(RemoteConfigClient, OnNetworkApiErrorTheExceptionsFlows) -{ - bool exception_thrown = false; - auto api = std::make_unique(); - EXPECT_CALL(*api, get_configs) - .WillOnce(Throw(dds::remote_config::network_exception("some"))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - try { - api_client.poll(); - } catch (dds::remote_config::network_exception & /** e*/) { - exception_thrown = true; - } - - EXPECT_TRUE(exception_thrown); -} - -std::string sort_arrays(std::string json) -{ - rapidjson::Document doc; - doc.Parse(json); - - // Sorting products - auto products = doc.FindMember("client") - ->value.FindMember("products") - ->value.GetArray(); - std::sort(products.begin(), products.end(), - [](const rapidjson::Value &lhs, const rapidjson::Value &rhs) { - return strcmp(lhs.GetString(), rhs.GetString()) < 0; - }); - - // Sorting config_states - auto config_states = doc.FindMember("client") - ->value.FindMember("state") - ->value.FindMember("config_states") - ->value.GetArray(); - std::sort(config_states.begin(), config_states.end(), - [](const rapidjson::Value &lhs, const rapidjson::Value &rhs) { - auto first = lhs.FindMember("id")->value.GetString(); - auto second = rhs.FindMember("id")->value.GetString(); - return strcmp(first, second) < 0; - }); - - // Sorting cached_target_files - auto cached_target_files = - doc.FindMember("cached_target_files")->value.GetArray(); - std::sort(cached_target_files.begin(), cached_target_files.end(), - [](const rapidjson::Value &lhs, const rapidjson::Value &rhs) { - auto first = lhs.FindMember("path")->value.GetString(); - auto second = rhs.FindMember("path")->value.GetString(); - return strcmp(first, second) < 0; - }); - - // Generate string - dds::string_buffer buffer; - rapidjson::Writer writer(buffer); - if (!doc.Accept(writer)) { - return json; - } - - return buffer.get_string_ref(); -} - -TEST_F(RemoteConfigClient, ItCallsToApiOnPoll) -{ - auto api = std::make_unique(); - std::string request_sent = ""; - EXPECT_CALL(*api, get_configs(_)) - .Times(AtLeast(1)) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response(paths)))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(request_sent)); -} - -TEST_F(RemoteConfigClient, PollFailsWithoutRuntimeID) -{ - auto api = std::make_unique(); - EXPECT_CALL(*api, get_configs(_)).Times(0); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - - EXPECT_FALSE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, ReplaceRuntimeID) -{ - auto api = std::make_unique(); - std::string request_sent = ""; - EXPECT_CALL(*api, get_configs(_)) - .Times(AtLeast(1)) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response(paths)))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Unregister the old ID and register a new one - api_client.unregister_runtime_id(runtime_id); - runtime_id = "something else"; - api_client.register_runtime_id(runtime_id); - api_client.register_runtime_id("dummy"); - api_client.register_runtime_id("unused"); - api_client.register_runtime_id("irrelevant"); - - EXPECT_TRUE(api_client.poll()); - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(request_sent)); -} - -TEST_F(RemoteConfigClient, RemoveRuntimeID) -{ - auto api = std::make_unique(); - std::string request_sent = ""; - EXPECT_CALL(*api, get_configs(_)) - .Times(AtLeast(1)) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response(paths)))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Unregister the ID, it should still be used - api_client.unregister_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(request_sent)); -} - -TEST_F(RemoteConfigClient, ItReturnErrorWhenApiNotProvided) -{ - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, nullptr, std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_FALSE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, ItReturnErrorWhenResponseIsInvalidJson) -{ - auto api = std::make_unique(); - EXPECT_CALL(*api, get_configs).WillOnce(Return("invalid json here")); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_FALSE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, - ItReturnErrorAndSaveLastErrorWhenClientConfigPathNotInTargetPaths) -{ - std::string response = generate_example_response(paths, paths, {}); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "missing config " + paths[0] + - " in " - "targets")); -} - -TEST_F(RemoteConfigClient, - ItReturnErrorAndSaveLastErrorWhenClientConfigPathNotInTargetFiles) -{ - std::string response = generate_example_response(paths, {}, paths); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "missing config " + paths[0] + - " in " - "target files and in cache files")); -} - -TEST(ClientConfig, ItGetGeneratedFromString) -{ - std::string apm_sampling = "APM_SAMPLING"; - auto cp = remote_config::config_path::from_path( - "datadog/2/LIVE_DEBUGGING/9e413cda-647b-335b-adcd-7ce453fc2284/config"); - EXPECT_EQ("LIVE_DEBUGGING", cp.product); - EXPECT_EQ("9e413cda-647b-335b-adcd-7ce453fc2284", cp.id); - - cp = remote_config::config_path::from_path( - "employee/DEBUG_DD/2.test1.config/config"); - EXPECT_EQ("DEBUG_DD", cp.product); - EXPECT_EQ("2.test1.config", cp.id); - - cp = remote_config::config_path::from_path( - "datadog/55/APM_SAMPLING/dynamic_rates/something"); - EXPECT_EQ(apm_sampling, cp.product); - EXPECT_EQ("dynamic_rates", cp.id); -} - -TEST(ClientConfig, ItDoesNotGetGeneratedFromStringIfNotValidMatch) -{ - bool exception = false; - - try { - remote_config::config_path::from_path(""); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path("invalid"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path("datadog/55/APM_SAMPLING/config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path( - "datadog/55/APM_SAMPLING//config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path( - "datadog/aa/APM_SAMPLING/dynamic_rates/config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path( - "something/APM_SAMPLING/dynamic_rates/config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); -} - -TEST_F(RemoteConfigClient, ItReturnsErrorWhenClientConfigPathCantBeParsed) -{ - std::string invalid_path = "invalid/path/dynamic_rates/config"; - std::string response = generate_example_response({invalid_path}); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error( - request_sent, true, "error parsing path " + invalid_path)); -} - -TEST_F(RemoteConfigClient, ItReturnsErrorIfProductOnPathNotRequested) -{ - std::string path_of_no_requested_product = - "datadog/2/APM_SAMPLING/dynamic_rates/config"; - std::string response = - generate_example_response({path_of_no_requested_product}); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings)); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "received config " + path_of_no_requested_product + - " for a " - "product that was not requested")); -} - -TEST_F(RemoteConfigClient, ItGeneratesClientStateAndCacheFromResponse) -{ - auto api = std::make_unique(); - - std::string first_request = ""; - std::string second_request = ""; - - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillOnce(DoAll(testing::SaveArg<0>(&first_request), - Return(generate_example_response(paths)))) - .WillOnce(DoAll(testing::SaveArg<0>(&second_request), - Return(generate_example_response(paths)))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - // First call should not contain state neither cache - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(first_request)); - // Second call. This should contain state and cache from previous - EXPECT_EQ(sort_arrays(generate_request_serialized(true, true)), - sort_arrays(second_request)); -} - -TEST_F(RemoteConfigClient, WhenANewConfigIsAddedItCallsOnUpdateOnPoll) -{ - auto api = std::make_unique(); - - std::string response = generate_example_response({first_path}); - - EXPECT_CALL(*api, get_configs(_)).Times(1).WillOnce(Return(response)); - - std::string content = test_helpers::raw_from_path(first_path); - std::unordered_map hashes = { - std::pair( - "sha256", test_helpers::sha256_from_path(first_path))}; - remote_config::config expected_config = {first_product_product, - first_product_id, content, first_path, hashes, - test_helpers::version_from_path(first_path), - test_helpers::length_from_path(first_path), - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - // Product on response - auto listener01 = std::make_shared(); - EXPECT_CALL(*listener01, init()).Times(1); - EXPECT_CALL(*listener01, on_update(expected_config)).Times(1); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - EXPECT_CALL(*listener01, commit()).Times(1); - listener01->name = first_product_product; - - // Product on response - auto listener_called_no_configs01 = std::make_shared(); - EXPECT_CALL(*listener_called_no_configs01, init()).Times(1); - EXPECT_CALL(*listener_called_no_configs01, on_update(_)).Times(0); - EXPECT_CALL(*listener_called_no_configs01, on_unapply(_)).Times(0); - EXPECT_CALL(*listener_called_no_configs01, commit()).Times(1); - std::string product_str_not_in_response = "NOT_IN_RESPONSE"; - listener_called_no_configs01->name = product_str_not_in_response; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client(id, std::move(api), std::move(sid), - std::move(settings), {listener01, listener_called_no_configs01}); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, WhenAConfigDissapearOnFollowingPollsItCallsToUnApply) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string response02 = generate_example_response({second_path}); - - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillOnce(Return(response01)) - .WillOnce(Return(response02)); - - std::string content01 = test_helpers::raw_from_path(first_path); - std::unordered_map hashes01 = { - std::pair( - "sha256", test_helpers::sha256_from_path(first_path))}; - remote_config::config expected_config01 = {first_product_product, - first_product_id, content01, first_path, hashes01, - test_helpers::version_from_path(first_path), - test_helpers::length_from_path(first_path), - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - remote_config::config expected_config01_at_unapply = expected_config01; - expected_config01_at_unapply.apply_state = - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED; - - std::string content02 = test_helpers::raw_from_path(second_path); - std::unordered_map hashes02 = { - std::pair( - "sha256", test_helpers::sha256_from_path(second_path))}; - remote_config::config expected_config02 = {first_product_product, - second_product_id, content02, second_path, hashes02, - test_helpers::version_from_path(second_path), - test_helpers::length_from_path(second_path), - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - // Product on response - auto listener01 = std::make_shared(); - EXPECT_CALL(*listener01, init()).Times(2); - // First poll expectations - EXPECT_CALL(*listener01, on_update(expected_config01)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - // Second poll expectations - EXPECT_CALL(*listener01, on_update(expected_config02)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(expected_config01_at_unapply)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, commit()).Times(2); - // First poll expectations - listener01->name = first_product_product; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), {listener01}); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); -} - -TEST_F( - RemoteConfigClient, WhenAConfigGetsUpdatedOnFollowingPollsItCallsToUnUpdate) -{ - auto api = std::make_unique(); - - std::string response01( - "{\"roots\": [], \"targets\": " - "\"eyAgIAogICAgInNpZ25lZCI6IHsKICAgICAgICAiX3R5cGUiOiAidGFyZ2V0cyIsCiAg" - "ICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIj" - "ogInNvbWV0aGluZyIKICAgICAgICB9LAogICAgICAgICJ0YXJnZXRzIjogewogICAgICAg" - "ICAgICAiZGF0YWRvZy8yL0FQTV9TQU1QTElORy9keW5hbWljX3JhdGVzL2NvbmZpZyI6IH" - "sKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgInYi" - "OiAzNjc0MAogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOi" - "B7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICIwNzQ2NWNlY2U0N2U0NTQyYWJj" - "MGRhMDQwZDllYmI0MmVjOTcyMjQ5MjBkNjg3MDY1MWRjMzMxNjUyODYwOWQ1IgogICAgIC" - "AgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiA2NjM5OQogICAgICAg" - "ICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn" - "0=\", \"target_files\": [{\"path\": " - "\"datadog/2/APM_SAMPLING/dynamic_rates/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"} ], " - "\"client_configs\": " - "[\"datadog/2/APM_SAMPLING/dynamic_rates/config\"] " - "}"); - - std::string response02( - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduZWQiOiB7CiAgICAgICAgICAgICAgICAiX3R5cGUiOiAidGFy" - "Z2V0cyIsCiAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgIC" - "AgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAic29tZXRoaW5nIgogICAgICAgICAg" - "ICAgICAgfSwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgICAgICAgIC" - "AgICAgICAgICAiZGF0YWRvZy8yL0FQTV9TQU1QTElORy9keW5hbWljX3JhdGVzL2NvbmZp" - "ZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogIC" - "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAzNjc0MAogICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICJzaGEyNTYiOiAiYW5vdGhlcl9oYXNoX2hlcmUiCiAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgIC" - "AgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAg" - "ICB9Cn0=\", \"target_files\": [{\"path\": " - "\"datadog/2/APM_SAMPLING/dynamic_rates/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"} ], " - "\"client_configs\": " - "[\"datadog/2/APM_SAMPLING/dynamic_rates/config\"] " - "}"); - - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillOnce(Return(response01)) - .WillOnce(Return(response02)); - - std::string product_str = "APM_SAMPLING"; - std::string product_str_01 = product_str; - std::string product_str_02 = product_str; - std::string id_product = "dynamic_rates"; - std::string id_product_01 = id_product; - std::string id_product_02 = id_product; - std::string path = "datadog/2/APM_SAMPLING/dynamic_rates/config"; - std::string path_01 = path; - std::string path_02 = path; - std::string content = - "UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo="; - std::string content_01 = content; - std::string content_02 = content; - std::unordered_map hashes_01 = {std::pair< - std::string, std::string>("sha256", - "07465cece47e4542abc0da040d9ebb42ec97224920d6870651dc3316528609d5")}; - remote_config::config expected_config = {product_str_01, id_product_01, - content_01, path_01, hashes_01, 36740, 66399, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - std::unordered_map hashes_02 = { - std::pair("sha256", "another_hash_here")}; - remote_config::config expected_config_02 = {product_str_02, id_product_02, - content_02, path_02, hashes_02, 36740, 66399, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - // Product on response - auto listener01 = std::make_shared(); - EXPECT_CALL(*listener01, init()).Times(2); - // Second poll expectations - EXPECT_CALL(*listener01, on_update(expected_config_02)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - // First poll expectations - EXPECT_CALL(*listener01, on_update(expected_config)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - EXPECT_CALL(*listener01, commit()).Times(2); - listener01->name = apm_sampling; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), {listener01}); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, FilesThatAreInCacheAreUsedWhenNotInTargetFiles) -{ - auto api = std::make_unique(); - - std::string first_request = ""; - std::string second_request = ""; - std::string third_request = ""; - - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(DoAll(testing::SaveArg<0>(&first_request), - Return(generate_example_response(paths)))) - .WillOnce(DoAll(testing::SaveArg<0>(&second_request), - Return(generate_example_response(paths, {}, paths)))) - .WillOnce(DoAll(testing::SaveArg<0>(&third_request), - Return(generate_example_response(paths, {}, paths)))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - // First call should not contain state neither cache - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(first_request)); - // Second call. Since this call has cache, response comes without - // target_files - EXPECT_EQ(sort_arrays(generate_request_serialized(true, true)), - sort_arrays(second_request)); - // Third call. Cache and state should be kept even though - // target_files came empty on second - EXPECT_EQ(sort_arrays(generate_request_serialized(true, true)), - sort_arrays(third_request)); -} - -TEST_F(RemoteConfigClient, NotTrackedFilesAreDeletedFromCache) -{ - auto api = std::make_unique(); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(Return(generate_example_response(paths))) - .WillOnce(Return(generate_example_response({}))) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response({})))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - // Lets validate cached_target_files is empty - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - auto output_itr = serialized_doc.FindMember("cached_target_files"); - - EXPECT_FALSE(output_itr == serialized_doc.MemberEnd()); - EXPECT_TRUE(rapidjson::kArrayType == output_itr->value.GetType()); - EXPECT_EQ(0, output_itr->value.GetArray().Size()); -} - -TEST_F(RemoteConfigClient, TestHashIsDifferentFromTheCache) -{ - auto api = std::make_unique(); - - std::string first_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX2hhc2giCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9Cn0=\", " - "\"target_files\": [{\"path\": " - "\"employee/ASM_FEATURES/2.test1.config/config\", \"raw\": " - "\"some_raw=\"}" - "], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"]" - "}"; - - // This response has a cached file with different hash, it should not be - // used - std::string second_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX290aGVyX2hhc2giCiAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgIC" - "AgICAgfSwKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9" - "Cn0=" - "\", \"target_files\": [], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"] }"; - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(Return(first_response)) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(second_response))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_FALSE(api_client.poll()); - EXPECT_FALSE(api_client.poll()); - - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "missing config employee/ASM_FEATURES/2.test1.config/config in " - "target files and in cache files")); -} - -TEST_F(RemoteConfigClient, TestWhenFileGetsFromCacheItsCachedLenUsed) -{ - auto api = std::make_unique(); - - std::string first_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX2hhc2giCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9Cn0=\", " - "\"target_files\": [{\"path\": " - "\"employee/ASM_FEATURES/2.test1.config/config\", \"raw\": " - "\"some_raw=\"}" - "], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"]" - "}"; - - // This response has a cached file with different len and version, it - // should not be used - std::string second_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiA0CiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX2hhc2giCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNTUKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9Cn0=\", " - "\"target_files\": [], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"] }"; - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(Return(first_response)) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(second_response))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - // Lets validate cached_target_files is empty - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - auto output_itr = serialized_doc.FindMember("cached_target_files"); - - auto files_cached = output_itr->value.GetArray(); - EXPECT_FALSE(output_itr == serialized_doc.MemberEnd()); - EXPECT_TRUE(rapidjson::kArrayType == output_itr->value.GetType()); - EXPECT_EQ(1, files_cached.Size()); - - auto len_itr = files_cached[0].FindMember("length"); - EXPECT_FALSE(len_itr == files_cached[0].MemberEnd()); - EXPECT_TRUE(rapidjson::kNumberType == len_itr->value.GetType()); - EXPECT_EQ(41, len_itr->value.GetInt()); -} - -rapidjson::GenericArray>::ValueType> -get_config_states(const rapidjson::Document &serialized_doc) -{ - return serialized_doc.FindMember("client") - ->value.FindMember("state") - ->value.FindMember("config_states") - ->value.GetArray(); -} - -TEST_F(RemoteConfigClient, ProductsWithAListenerAcknowledgeUpdates) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response01))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - - auto config_states_arr = get_config_states(serialized_doc); - - EXPECT_EQ(1, config_states_arr.Size()); - EXPECT_EQ( - (int)remote_config::protocol::config_state::applied_state::ACKNOWLEDGED, - config_states_arr[0].FindMember("apply_state")->value.GetInt()); - EXPECT_EQ("", - std::string( - config_states_arr[0].FindMember("apply_error")->value.GetString())); -} - -TEST_F(RemoteConfigClient, WhenAListerCanProccesAnUpdateTheConfigStateGetsError) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response01))); - - auto listener = std::make_shared(); - EXPECT_CALL(*listener, init()).Times(2); - EXPECT_CALL(*listener, on_update(_)) - .WillRepeatedly(mock::ThrowErrorApplyingConfig()); - EXPECT_CALL(*listener, commit()).Times(2); - - listener->name = first_product_product; - std::vector listeners = { - listener}; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client(id, std::move(api), std::move(sid), - std::move(settings), std::move(listeners)); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - - auto config_states_arr = get_config_states(serialized_doc); - - EXPECT_EQ(1, config_states_arr.Size()); - EXPECT_EQ((int)remote_config::protocol::config_state::applied_state::ERROR, - config_states_arr[0].FindMember("apply_state")->value.GetInt()); - EXPECT_EQ("some error", - std::string( - config_states_arr[0].FindMember("apply_error")->value.GetString())); -} - -TEST_F(RemoteConfigClient, OneClickActivationIsSetAsCapability) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(1) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response01))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - auto capabilities = - serialized_doc.FindMember("client")->value.FindMember("capabilities"); - - EXPECT_STREQ("AAI=", capabilities->value.GetString()); -} -} // namespace dds diff --git a/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp b/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp index 5d1af34b73..927756503f 100644 --- a/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "../../common.hpp" +#include "../mocks.hpp" #include "base64.h" #include "remote_config/exception.hpp" #include "remote_config/listeners/asm_features_listener.hpp" @@ -12,22 +13,13 @@ namespace dds { -remote_config::config get_config(const std::string &content, bool encode = true) -{ - std::string encoded_content = content; - if (encode) { - encoded_content = base64_encode(content); - } - - return {"some product", "some id", encoded_content, "some path", {}, 123, - 321, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; -} +namespace mock = remote_config::mock; +auto &ASM_FEATURES = remote_config::known_products::ASM_FEATURES; remote_config::config get_config_with_status(std::string status) { - return get_config("{\"asm\":{\"enabled\":" + status + "}}"); + return mock::get_config( + ASM_FEATURES, "{\"asm\":{\"enabled\":" + status + "}}"); } remote_config::config get_enabled_config(bool as_string = true) @@ -138,7 +130,7 @@ TEST(RemoteConfigAsmFeaturesListener, std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; remote_config::config non_base_64_content_config = - get_config(invalid_content, false); + mock::get_config(ASM_FEATURES, invalid_content); try { listener.on_update(non_base_64_content_config); @@ -160,7 +152,8 @@ TEST(RemoteConfigAsmFeaturesListener, auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); std::string invalid_content = "invalidJsonContent"; - remote_config::config config = get_config(invalid_content); + remote_config::config config = + mock::get_config(ASM_FEATURES, invalid_content); try { listener.on_update(config); @@ -181,7 +174,8 @@ TEST(RemoteConfigAsmFeaturesListener, ListenerThrowsAnErrorWhenAsmKeyMissing) "Invalid config json encoded contents: asm key missing or invalid"; auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); - remote_config::config asm_key_missing = get_config("{}"); + remote_config::config asm_key_missing = + mock::get_config(ASM_FEATURES, "{}"); try { listener.on_update(asm_key_missing); @@ -201,7 +195,8 @@ TEST(RemoteConfigAsmFeaturesListener, ListenerThrowsAnErrorWhenAsmIsNotValid) "Invalid config json encoded contents: asm key missing or invalid"; auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); - remote_config::config invalid_asm_key = get_config("{ \"asm\": 123}"); + remote_config::config invalid_asm_key = + mock::get_config(ASM_FEATURES, "{ \"asm\": 123}"); try { listener.on_update(invalid_asm_key); @@ -222,7 +217,8 @@ TEST( "Invalid config json encoded contents: enabled key missing"; auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); - remote_config::config enabled_key_missing = get_config("{ \"asm\": {}}"); + remote_config::config enabled_key_missing = + mock::get_config(ASM_FEATURES, "{ \"asm\": {}}"); try { listener.on_update(enabled_key_missing); @@ -244,7 +240,7 @@ TEST(RemoteConfigAsmFeaturesListener, auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); remote_config::config enabled_key_invalid = - get_config("{ \"asm\": { \"enabled\": 123}}"); + mock::get_config(ASM_FEATURES, "{ \"asm\": { \"enabled\": 123}}"); try { listener.on_update(enabled_key_invalid); diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp index 987d0edfd0..d04f80dc39 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp @@ -9,12 +9,13 @@ #include "json_helper.hpp" #include "remote_config/exception.hpp" #include "remote_config/listeners/config_aggregators/asm_aggregator.hpp" +#include "remote_config/product.hpp" #include namespace dds::remote_config { namespace { -using mock::generate_config; +using mock::get_config; const std::string waf_rule = R"({"version":"2.1","rules":[{"id":"1","name":"rule1","tags":{"type":"flow1","category":"category1"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg1","key_path":[]}],"regex":".*"}}]}]})"; @@ -59,8 +60,8 @@ TEST(RemoteConfigAsmAggregator, EmptyConfigThrows) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - EXPECT_THROW(aggregator.add(generate_config("ASM", {})), - remote_config::error_applying_config); + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, {})), + std::runtime_error); // mmap failure aggregator.aggregate(doc); @@ -91,7 +92,7 @@ TEST(RemoteConfigAsmAggregator, IncorrectTypeThrows) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - EXPECT_THROW(aggregator.add(generate_config("ASM", rule_override)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, rule_override)), remote_config::error_applying_config); aggregator.aggregate(doc); @@ -121,7 +122,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideEmpty) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -150,7 +151,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideSingleConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -193,10 +194,10 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideMultipleConfigs) rapidjson::Document doc(rapidjson::kObjectType); remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -241,15 +242,15 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideIgnoreInvalidConfigs) rapidjson::Document doc(rapidjson::kObjectType); remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); { const std::string invalid = R"({"rules_override": {"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -295,7 +296,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverridesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -335,9 +336,9 @@ TEST(RemoteConfigAsmAggregator, RulesOverridesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config(known_products::ASM, rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -384,7 +385,7 @@ TEST(RemoteConfigAsmAggregator, ActionsSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -413,9 +414,9 @@ TEST(RemoteConfigAsmAggregator, ActionsMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -444,15 +445,15 @@ TEST(RemoteConfigAsmAggregator, ActionsIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); { const std::string invalid = R"({"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -482,9 +483,9 @@ TEST(RemoteConfigAsmAggregator, ActionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -507,7 +508,7 @@ TEST(RemoteConfigAsmAggregator, ActionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config(known_products::ASM, action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -537,7 +538,7 @@ TEST(RemoteConfigAsmAggregator, ExclusionsSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -566,10 +567,10 @@ TEST(RemoteConfigAsmAggregator, ExclusionsMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -598,15 +599,15 @@ TEST(RemoteConfigAsmAggregator, ExclusionsIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); { const std::string invalid = R"({"exclusions": {"id":1,"rules_target":[{"rule_id":1}]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -636,10 +637,10 @@ TEST(RemoteConfigAsmAggregator, ExclusionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -662,7 +663,7 @@ TEST(RemoteConfigAsmAggregator, ExclusionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -692,7 +693,7 @@ TEST(RemoteConfigAsmAggregator, CustomRulesSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -721,15 +722,15 @@ TEST(RemoteConfigAsmAggregator, CustomRulesMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); { const std::string invalid = R"({"custom_rules": {"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -758,10 +759,10 @@ TEST(RemoteConfigAsmAggregator, CustomRulesIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -791,10 +792,10 @@ TEST(RemoteConfigAsmAggregator, CustomRulesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -817,7 +818,7 @@ TEST(RemoteConfigAsmAggregator, CustomRulesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -847,7 +848,7 @@ TEST(RemoteConfigAsmAggregator, AllSingleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -876,23 +877,23 @@ TEST(RemoteConfigAsmAggregator, IgnoreInvalidConfigs) { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); } { const std::string update = R"({"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", update)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, update)), remote_config::error_applying_config); } { const std::string update = R"({"custom_rules":[{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); } aggregator.aggregate(doc); @@ -922,23 +923,23 @@ TEST(RemoteConfigAsmAggregator, IgnoreInvalidOverlappingConfigs) { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}],"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}],"custom_rules":[{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config(known_products::ASM, update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}],"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", update)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, update)), remote_config::error_applying_config); } { const std::string update = R"({"custom_rules":{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", update)), + EXPECT_THROW(aggregator.add(get_config(known_products::ASM, update)), remote_config::error_applying_config); } aggregator.aggregate(doc); diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp index 30a17de218..f9573d6135 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp @@ -15,7 +15,7 @@ namespace dds::remote_config { -using mock::generate_config; +using mock::get_config; struct test_rule_data_data { std::optional expiration; @@ -62,7 +62,7 @@ remote_config::config get_rules_data(std::vector data) rapidjson::Writer writer(buffer); document.Accept(writer); - return generate_config("ASM_DATA", buffer.get_string_ref()); + return get_config(known_products::ASM_DATA, buffer.get_string_ref()); } TEST(RemoteConfigAsmDataAggregator, ParseRulesData) @@ -335,7 +335,8 @@ TEST(RemoteConfigAsmDataAggregator, IgnoreInvalidConfigs) { const std::string &invalid = R"({"rules_data": [{"id": "id01", "data": [{"expiration": 11, "value": "1.2.3.5"} ], "type": "ip_with_expiration"},{"data": [{"expiration": 11111, "value": "1.2.3.4"} ], "type": "ip_with_expiration"}]})"; - EXPECT_THROW(aggregator.add(generate_config("ASM_DATA", invalid)), + EXPECT_THROW( + aggregator.add(get_config(known_products::ASM_DATA, invalid)), remote_config::error_applying_config); } aggregator.aggregate(doc); @@ -415,7 +416,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfContentNotInBase64) std::string invalid_content = "&&&"; std::string expected_error_message = "Invalid config contents"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, false); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -439,7 +440,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfContentNotValidJsonContent) std::string invalid_content = "InvalidJsonContent"; std::string expected_error_message = "Invalid config contents"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -465,7 +466,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoRulesDataKey) "Invalid config json contents: rules_data key missing or " "invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -491,7 +492,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfRulesDataNotArray) "Invalid config json contents: rules_data key missing or " "invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -516,7 +517,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfRulesDataEntryNotObject) std::string expected_error_message = "Invalid config json contents: rules_data entry invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -544,7 +545,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoId) "Invalid config json contents: rules_data missing a field or " "field is invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -572,7 +573,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfIdNotString) "Invalid config json contents: rules_data missing a field or " "field is invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -600,7 +601,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoType) "Invalid config json contents: rules_data missing a field or " "field is invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -628,7 +629,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfTypeNotString) "Invalid config json contents: rules_data missing a field or " "field is invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -655,7 +656,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoData) "Invalid config json contents: rules_data missing a field or " "field is invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -683,7 +684,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataNotArray) "Invalid config json contents: rules_data missing a field or " "field is invalid"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -709,7 +710,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataEntryNotObject) std::string expected_error_message = "Invalid config json contents: Entry on data not a valid object"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -734,7 +735,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataExpirationHasInvalidType) R"({"rules_data": [{"data": [{"expiration": "invalid", "value": "1.2.3.4"}], "id": "some_id", "type": "data_with_expiration"}]})"; std::string expected_error_message = "Invalid type for expiration entry"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -760,7 +761,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataValueMissing) "\"some_id\", \"type\": \"data_with_expiration\"} ] }"; std::string expected_error_message = "Invalid value of data entry"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; @@ -787,7 +788,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataValueHasInvalidType) "\"ip_with_expiration\"} ] }"; std::string expected_error_message = "Invalid value of data entry"; remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + get_config(known_products::ASM_DATA, invalid_content); remote_config::asm_data_aggregator aggregator; diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp index 964ab22b3b..afa9f055f7 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp @@ -16,7 +16,7 @@ const std::string waf_rule = namespace dds::remote_config { -using mock::generate_config; +using mock::get_config; TEST(RemoteConfigAsmDdAggregator, AddConfig) { @@ -24,7 +24,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM_DD", waf_rule)); + aggregator.add(get_config(known_products::ASM_DD, waf_rule)); aggregator.aggregate(doc); const auto &rules = doc["rules"]; @@ -38,7 +38,7 @@ TEST(RemoteConfigAsmDdAggregator, RemoveConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.remove(generate_config("ASM_DD", waf_rule)); + aggregator.remove(get_config(known_products::ASM_DD, waf_rule)); aggregator.aggregate(doc); const auto &rules = doc["rules"]; @@ -56,7 +56,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfigInvalidBase64Content) std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; remote_config::config config = - generate_config("ASM_DD", invalid_content, false); + get_config(known_products::ASM_DD, invalid_content); remote_config::asm_dd_aggregator aggregator; rapidjson::Document doc(rapidjson::kObjectType); @@ -80,7 +80,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfigInvalidJsonContent) std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; remote_config::config config = - generate_config("ASM_DD", invalid_content, true); + get_config(known_products::ASM_DD, invalid_content); remote_config::asm_dd_aggregator aggregator; rapidjson::Document doc(rapidjson::kObjectType); diff --git a/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp b/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp index 44905a1c37..3c75044cc2 100644 --- a/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp @@ -7,11 +7,13 @@ #include "../../common.hpp" #include "../mocks.hpp" #include "base64.h" +#include "engine.hpp" #include "json_helper.hpp" #include "remote_config/exception.hpp" #include "remote_config/listeners/engine_listener.hpp" #include "remote_config/product.hpp" #include "subscriber/waf.hpp" +#include #include const std::string waf_rule = @@ -19,7 +21,7 @@ const std::string waf_rule = namespace dds::remote_config { -using mock::generate_config; +using mock::get_config; namespace { @@ -55,7 +57,8 @@ TEST(RemoteConfigEngineListener, UnknownConfig) remote_config::engine_listener listener(engine); listener.init(); - EXPECT_THROW(listener.on_update(generate_config("UNKNOWN", waf_rule)), + EXPECT_THROW( + listener.on_update(get_config(known_products::UNKNOWN, waf_rule)), error_applying_config); listener.commit(); } @@ -72,7 +75,7 @@ TEST(RemoteConfigEngineListener, RuleUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); listener.commit(); { @@ -102,7 +105,7 @@ TEST(RemoteConfigEngineListener, RuleUpdateFallback) remote_config::engine_listener listener(engine, create_sample_rules_ok()); listener.init(); - listener.on_unapply(generate_config("ASM_DD", waf_rule)); + listener.on_unapply(get_config(known_products::ASM_DD, waf_rule)); listener.commit(); { @@ -135,7 +138,7 @@ TEST(RemoteConfigEngineListener, RulesOverrideUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -176,8 +179,8 @@ TEST(RemoteConfigEngineListener, RulesAndRulesOverrideUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -225,7 +228,7 @@ TEST(RemoteConfigEngineListener, ExclusionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -267,8 +270,8 @@ TEST(RemoteConfigEngineListener, RulesAndExclusionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -317,7 +320,7 @@ TEST(RemoteConfigEngineListener, ActionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -360,8 +363,8 @@ TEST(RemoteConfigEngineListener, RulesAndActionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -412,7 +415,7 @@ TEST(RemoteConfigEngineListener, CustomRulesUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -457,8 +460,8 @@ TEST(RemoteConfigEngineListener, RulesAndCustomRulesUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config(known_products::ASM, update)); listener.commit(); { @@ -506,7 +509,7 @@ TEST(RemoteConfigEngineListener, RulesDataUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config(known_products::ASM_DATA, update)); listener.commit(); { @@ -540,8 +543,8 @@ TEST(RemoteConfigEngineListener, RulesAndRuleDataUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config(known_products::ASM_DATA, update)); listener.commit(); { @@ -578,11 +581,11 @@ TEST(RemoteConfigEngineListener, FullUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); { const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config(known_products::ASM_DATA, update)); } { const std::string update = @@ -591,23 +594,23 @@ TEST(RemoteConfigEngineListener, FullUpdate) {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } { const std::string update = R"({"actions": [{"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } listener.commit(); @@ -634,11 +637,11 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) remote_config::engine_listener listener(engine, create_sample_rules_ok()); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); { const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config(known_products::ASM_DATA, update)); } listener.commit(); @@ -673,12 +676,12 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } listener.commit(); @@ -714,17 +717,17 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) } listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config(known_products::ASM_DD, waf_rule)); { const std::string update = R"({"actions": [{"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); } listener.commit(); @@ -776,7 +779,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdate) std::map meta; std::map metrics; - auto e{engine::create()}; + std::shared_ptr e{engine::create()}; e->subscribe(waf::instance::from_string(rules, meta, metrics)); { @@ -797,7 +800,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdate) remote_config::engine_listener listener(e); listener.init(); - listener.on_update(generate_config("ASM_DD", new_rules)); + listener.on_update(get_config(known_products::ASM_DD, new_rules)); listener.commit(); { @@ -823,7 +826,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdateFallback) std::map meta; std::map metrics; - auto e{engine::create()}; + std::shared_ptr e{engine::create()}; e->subscribe(waf::instance::from_string(rules, meta, metrics)); { @@ -840,7 +843,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdateFallback) remote_config::engine_listener listener(e, create_sample_rules_ok()); listener.init(); - listener.on_unapply(generate_config("ASM_DD", "")); + listener.on_unapply(get_config(known_products::ASM_DD, "")); listener.commit(); { @@ -859,7 +862,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideUpdateDisableRule) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -877,7 +880,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideUpdateDisableRule) const std::string rule_override = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(generate_config("ASM", rule_override)); + listener.on_update(get_config(known_products::ASM, rule_override)); { auto ctx = engine->get_context(); @@ -906,7 +909,7 @@ TEST(RemoteConfigEngineListener, RuleOverrideUpdateSetOnMatch) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -926,7 +929,7 @@ TEST(RemoteConfigEngineListener, RuleOverrideUpdateSetOnMatch) const std::string rule_override = R"({"rules_override": [{"rules_target": [{"tags": {"type": "flow1"}}], "on_match": ["block"]}]})"; - listener.on_update(generate_config("ASM", rule_override)); + listener.on_update(get_config(known_products::ASM, rule_override)); { auto ctx = engine->get_context(); @@ -957,7 +960,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideAndActionsUpdate) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -979,7 +982,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideAndActionsUpdate) {"status_code": "303", "location": "localhost"}}],"rules_override": [{"rules_target": [{"rule_id": "1"}], "on_match": ["redirect"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); { auto ctx = engine->get_context(); @@ -1010,7 +1013,7 @@ TEST(RemoteConfigEngineListener, EngineExclusionsUpdatePasslistRule) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -1029,7 +1032,7 @@ TEST(RemoteConfigEngineListener, EngineExclusionsUpdatePasslistRule) const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); { auto ctx = engine->get_context(); @@ -1058,7 +1061,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -1090,7 +1093,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) "category":"custom"},"conditions":[{"operator":"match_regex","parameters": {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config(known_products::ASM, update)); { auto ctx = engine->get_context(); @@ -1134,7 +1137,8 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) } listener.init(); - listener.on_update(generate_config("ASM", R"({"custom_rules":[]})")); + listener.on_update( + get_config(known_products::ASM, R"({"custom_rules":[]})")); listener.commit(); { @@ -1168,7 +1172,7 @@ TEST(RemoteConfigEngineListener, EngineRuleDataUpdate) std::map meta; std::map metrics; - auto e{engine::create()}; + std::shared_ptr e{engine::create()}; e->subscribe(waf::instance::from_string(waf_rule_with_data, meta, metrics)); remote_config::engine_listener listener(e); @@ -1186,7 +1190,7 @@ TEST(RemoteConfigEngineListener, EngineRuleDataUpdate) const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config(known_products::ASM_DATA, update)); { auto ctx = e->get_context(); diff --git a/appsec/tests/helper/remote_config/mocks.cpp b/appsec/tests/helper/remote_config/mocks.cpp new file mode 100644 index 0000000000..c7fc1729a3 --- /dev/null +++ b/appsec/tests/helper/remote_config/mocks.cpp @@ -0,0 +1,40 @@ +#include "mocks.hpp" +#include +#include +#include +#include + +namespace dds::remote_config::mock { +remote_config::config get_config(product p, const std::string &content) +{ + static std::atomic id{0}; + + const int cur_id = id.fetch_add(1, std::memory_order_relaxed); + + std::string shm_path{"/test-rc-file-" + std::to_string(cur_id)}; + ::shm_unlink(shm_path.c_str()); + int shm_fd = ::shm_open(shm_path.c_str(), O_CREAT | O_RDWR, 0600); + if (shm_fd == -1) { + std::abort(); + } + ::ftruncate(shm_fd, content.size()); + + if (content.size() > 0) { + void *mem = + ::mmap(nullptr, content.size(), PROT_WRITE, MAP_SHARED, shm_fd, 0); + if (mem == MAP_FAILED) { + std::abort(); + } + std::copy_n(content.data(), content.size(), static_cast(mem)); + + if (::munmap(mem, content.size()) != 0) { + std::abort(); + } + } + + ::close(shm_fd); + + return {shm_path, std::string{"datadog/2/"} + std::string{p.name()} + + "/foobar_" + std::to_string(cur_id) + "/config"}; +} +} // namespace dds::remote_config::mock diff --git a/appsec/tests/helper/remote_config/mocks.hpp b/appsec/tests/helper/remote_config/mocks.hpp index c142c89bf1..b1cbb0d969 100644 --- a/appsec/tests/helper/remote_config/mocks.hpp +++ b/appsec/tests/helper/remote_config/mocks.hpp @@ -11,7 +11,6 @@ #include "engine.hpp" #include "remote_config/client.hpp" #include "remote_config/config.hpp" -#include "service_identifier.hpp" namespace dds::remote_config::mock { @@ -30,30 +29,6 @@ class engine : public dds::engine { static auto create() { return std::shared_ptr(new engine()); } }; -class client : public remote_config::client { -public: - client(service_identifier sid) - : remote_config::client(nullptr, std::move(sid), {}) - {} - ~client() override = default; - MOCK_METHOD0(poll, bool()); - MOCK_METHOD0(is_remote_config_available, bool()); - MOCK_METHOD(void, register_runtime_id, (const std::string &id), (override)); - MOCK_METHOD( - void, unregister_runtime_id, (const std::string &id), (override)); -}; - -inline remote_config::config generate_config( - const std::string &product, const std::string &content, bool encode = true) -{ - std::string encoded_content = content; - if (encode) { - encoded_content = base64_encode(content); - } - - return {product, "id", encoded_content, "path", {}, 123, 321, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; -} +remote_config::config get_config(product p, const std::string &content); } // namespace dds::remote_config::mock diff --git a/appsec/tests/helper/remote_config/parser_test.cpp b/appsec/tests/helper/remote_config/parser_test.cpp deleted file mode 100644 index 64765834e4..0000000000 --- a/appsec/tests/helper/remote_config/parser_test.cpp +++ /dev/null @@ -1,1227 +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 "../common.hpp" -#include "remote_config/protocol/tuf/parser.hpp" - -namespace dds { - -std::string get_example_response() -{ - std::string response( - "{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5aWQi" - "OiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0Mz" - "EyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdj" - "Y2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZj" - "c2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5" - "MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgIl" - "90eXBlIjogInRhcmdldHMiLAogICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgICJv" - "cGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam94TENKemRHRjBaU0k2ZX" - "lKbWFXeGxYMmhoYzJobGN5STZXeUpTS3pKRFZtdGxkRVJ6WVc1cFdrZEphMFphWkZKTlQy" - "RllhM1Z6TURGMWVsUTFNM3BuZW1sU1RHRTBQU0lzSWtJd1dtTTNUMUlyVWxWTGNuZE9iMF" - "ZFV2pZM1VYVjVXRWxyYTJjeGIyTkhWV1IzZWtac1MwZERaRlU5SWl3aWVIRnFUbFV4VFV4" - "WFUzQlJiRFpOYWt4UFUyTnZTVUoyYjNsU2VsWnJkelp6TkdFcmRYVndPV2d3UVQwaVhYMT" - "kiCiAgICAgICAgfSwKICAgICAgICAiZXhwaXJlcyI6ICIyMDIyLTExLTA0VDEzOjMxOjU5" - "WiIsCiAgICAgICAgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgInRhcmdldH" - "MiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0" - "ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgIC" - "AgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAg" - "ICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3NDY1Y2" - "VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZGMzMzE2NTI4" - "NjA5ZDUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcm" - "UwMSIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNjYz" - "OTkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRhdGFkb2cvMi9ERUJVRy9sdWtlLn" - "N0ZWVuc2VuL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAg" - "ICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImM2YThj" - "ZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZW" - "E3ZDg3NDAiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhl" - "cmUwMiIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMT" - "MKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImVtcGxveWVlL0RFQlVHX0RELzIudGVz" - "dDEuY29uZmlnL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgIC" - "AgICAgICAgICAgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAg" - "ICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZW" - "Q4MjU2NDdhZDBlYzZhNzg5OTE4ODkwNTY1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgz" - "Mzg5MTJkYWUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaG" - "hlcmUwMyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjog" - "NDEKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4Nz" - "E1NgogICAgfQp9\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - - return response; -} - -void assert_parser_error(std::function parser, - std::string payload, - remote_config::protocol::remote_config_parser_result result) -{ - remote_config::protocol::remote_config_parser_result error; - try { - parser(payload); - } catch (remote_config::protocol::parser_exception &e) { - error = e.get_error(); - } - - EXPECT_EQ(result, error); -} - -TEST(RemoteConfigParser, ItReturnsErrorWhenInvalidBodyIsGiven) -{ - assert_parser_error(remote_config::protocol::parse, "invalid_json", - remote_config::protocol::remote_config_parser_result::invalid_json); -} - -TEST(RemoteConfigParser, ParsingNonObjectPayloadThrowsException) -{ - assert_parser_error(remote_config::protocol::parse, "[]", - remote_config::protocol::remote_config_parser_result::invalid_response); -} - -TEST(RemoteConfigParser, TargetsFieldMustBeString) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"targets\": [], \"target_files\": [], \"client_configs\": [] }", - remote_config::protocol::remote_config_parser_result:: - targets_field_invalid_type); -} - -TEST(RemoteConfigParser, targetFilesFieldCanBeMissed) -{ - auto response = remote_config::protocol::parse( - "{\"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5" - "aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNz" - "FiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBm" - "NWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOG" - "IzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMy" - "NWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaW" - "duZWQiOiB7CiAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICJjdXN0" - "b20iOiB7CiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWl" - "hKemFXOXVJam94TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZXeUpTS3pK" - "RFZtdGxkRVJ6WVc1cFdrZEphMFphWkZKTlQyRllhM1Z6TURGMWVsUTFNM3BuZW1sU1" - "RHRTBQU0lzSWtJd1dtTTNUMUlyVWxWTGNuZE9iMFZFV2pZM1VYVjVXRWxyYTJjeGIy" - "TkhWV1IzZWtac1MwZERaRlU5SWl3aWVIRnFUbFV4VFV4WFUzQlJiRFpOYWt4UFUyTn" - "ZTVUoyYjNsU2VsWnJkelp6TkdFcmRYVndPV2d3UVQwaVhYMTkiCiAgICAgICAgfSwK" - "ICAgICAgICAiZXhwaXJlcyI6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgIC" - "AgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgInRhcmdldHMiOiB7CiAg" - "ICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0ZXMvY2" - "9uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAg" - "ICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3" - "NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZG" - "MzMzE2NTI4NjA5ZDUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hh" - "NTEyaGFzaGhlcmUwMSIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIC" - "AibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRhdGFk" - "b2cvMi9ERUJVRy9sdWtlLnN0ZWVuc2VuL2NvbmZpZyI6IHsKICAgICAgICAgICAgIC" - "AgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAg" - "ICAgICAgICB9LAogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgIC" - "AgICAgICAgICAic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5" - "Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiLAogICAgICAgICAgIC" - "AgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcmUwMiIKICAgICAgICAgICAg" - "ICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMTMKICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgImVtcGxveWVlL0RFQlVHX0RELzIudGVzdDEuY29uZmlnL2Nv" - "bmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgIC" - "AgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAg" - "Imhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZWQ4Mj" - "U2NDdhZDBlYzZhNzg5OTE4ODkwNTY1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgz" - "Mzg5MTJkYWUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaG" - "FzaGhlcmUwMyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb2" - "4iOiAyNzQ4NzE1NgogICAgfQp9\", \"client_configs\": [] }"); - ASSERT_TRUE(response.target_files.empty()); -} - -TEST(RemoteConfigParser, targetFilesFieldMustBeArray) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"targets\": \"\", \"target_files\": \"\", \"client_configs\": [] }", - remote_config::protocol::remote_config_parser_result:: - target_files_field_invalid_type); -} - -TEST(RemoteConfigParser, clientConfigsFieldCanBeMissed) -{ - auto response = remote_config::protocol::parse( - "{\"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5" - "aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNz" - "FiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBm" - "NWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOG" - "IzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMy" - "NWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaW" - "duZWQiOiB7CiAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICJjdXN0" - "b20iOiB7CiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWl" - "hKemFXOXVJam94TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZXeUpTS3pK" - "RFZtdGxkRVJ6WVc1cFdrZEphMFphWkZKTlQyRllhM1Z6TURGMWVsUTFNM3BuZW1sU1" - "RHRTBQU0lzSWtJd1dtTTNUMUlyVWxWTGNuZE9iMFZFV2pZM1VYVjVXRWxyYTJjeGIy" - "TkhWV1IzZWtac1MwZERaRlU5SWl3aWVIRnFUbFV4VFV4WFUzQlJiRFpOYWt4UFUyTn" - "ZTVUoyYjNsU2VsWnJkelp6TkdFcmRYVndPV2d3UVQwaVhYMTkiCiAgICAgICAgfSwK" - "ICAgICAgICAiZXhwaXJlcyI6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgIC" - "AgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgInRhcmdldHMiOiB7CiAg" - "ICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0ZXMvY2" - "9uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAg" - "ICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3" - "NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZG" - "MzMzE2NTI4NjA5ZDUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hh" - "NTEyaGFzaGhlcmUwMSIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIC" - "AibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRhdGFk" - "b2cvMi9ERUJVRy9sdWtlLnN0ZWVuc2VuL2NvbmZpZyI6IHsKICAgICAgICAgICAgIC" - "AgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAg" - "ICAgICAgICB9LAogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgIC" - "AgICAgICAgICAic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5" - "Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiLAogICAgICAgICAgIC" - "AgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcmUwMiIKICAgICAgICAgICAg" - "ICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMTMKICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgImVtcGxveWVlL0RFQlVHX0RELzIudGVzdDEuY29uZmlnL2Nv" - "bmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgIC" - "AgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAg" - "Imhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZWQ4Mj" - "U2NDdhZDBlYzZhNzg5OTE4ODkwNTY1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgz" - "Mzg5MTJkYWUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaG" - "FzaGhlcmUwMyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb2" - "4iOiAyNzQ4NzE1NgogICAgfQp9\", \"target_files\": [] }"); - - ASSERT_TRUE(response.client_configs.empty()); -} - -TEST(RemoteConfigParser, clientConfigsFieldMustBeArray) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"targets\": \"\", \"target_files\": [], \"client_configs\": \"\" }", - remote_config::protocol::remote_config_parser_result:: - client_config_field_invalid_type); -} - -TEST(RemoteConfigParser, TargetFilesAreParsed) -{ - std::string response = get_example_response(); - - auto gcr = remote_config::protocol::parse(response); - - EXPECT_EQ(2, gcr.target_files.size()); - - auto target_files = gcr.target_files; - - EXPECT_EQ("employee/DEBUG_DD/2.test1.config/config", - target_files.find("employee/DEBUG_DD/2.test1.config/config") - ->second.path); - EXPECT_EQ("UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=", - target_files.find("employee/DEBUG_DD/2.test1.config/config") - ->second.raw); - - EXPECT_EQ("datadog/2/DEBUG/luke.steensen/config", - target_files.find("datadog/2/DEBUG/luke.steensen/config")->second.path); - EXPECT_EQ("aGVsbG8gdmVjdG9yIQ==", - target_files.find("datadog/2/DEBUG/luke.steensen/config")->second.raw); -} - -TEST(RemoteConfigParser, TargetFilesWithoutPathAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{ \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_path_field_missing); -} - -TEST(RemoteConfigParser, TargetFilesWithNonStringPathAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": [], \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_path_field_invalid_type); -} - -TEST(RemoteConfigParser, TargetFilesWithoutRawAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\"} ], " - "\"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_raw_field_missing); -} - -TEST(RemoteConfigParser, TargetFilesWithNonNonStringRawAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": []} ], " - "\"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_raw_field_invalid_type); -} - -TEST(RemoteConfigParser, TargetFilesMustBeObjects) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [ " - "\"invalid\", " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_object_invalid); -} - -TEST(RemoteConfigParser, ClientConfigsAreParsed) -{ - std::string response = get_example_response(); - - auto gcr = remote_config::protocol::parse(response); - - EXPECT_EQ(2, gcr.client_configs.size()); - - auto client_configs = gcr.client_configs; - - EXPECT_EQ("datadog/2/DEBUG/luke.steensen/config", client_configs[0]); - EXPECT_EQ("employee/DEBUG_DD/2.test1.config/config", client_configs[1]); -} - -TEST(RemoteConfigParser, ClientConfigsMustBeStrings) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", " - "\"target_files\": [], \"client_configs\": " - "[[\"invalid\"], " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - client_config_field_invalid_entry); -} - -TEST(RemoteConfigParser, TargetsMustBeNotEmpty) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": \"\", " - "\"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_field_empty); -} - -TEST(RemoteConfigParser, TargetsnMustBeValidBase64Encoded) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": \"nonValid%Base64Here\", " - "\"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_field_invalid_base64); -} - -TEST(RemoteConfigParser, TargetsDecodedMustBeValidJson) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": \"nonJsonHere\", \"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_field_invalid_json); -} - -TEST(RemoteConfigParser, SignedFieldOnTargetsMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5aWQ" - "iOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0" - "MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjM" - "jdjY2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1Yj" - "FkZjc2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZ" - "kYTg5MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaWduZWQiOiAiaW52YWxp" - "ZCIKfQ==\", \"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, SignedFieldOnTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbICAgICAgICAKICAgIF0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - signed_targets_field_missing); -} - -TEST(RemoteConfigParser, _TypeFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgImN" - "1c3RvbSI6IHsKICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNvbWV0" - "aGluZyIKICAgICAgICB9LAogICAgICAgICJleHBpcmVzIjogIjIwMjItMTEtMDRUMTM6M" - "zE6NTlaIiwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidG" - "FyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fU0FNUExJTkcvZHluYW1" - "pY19yYXRlcy9jb25maWciOiB7CiAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAg" - "ICAgICAgICAgICAgICAgICJ2IjogMQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgI" - "CAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJibG" - "FoIiwKICAgICAgICAgICAgICAgICAgICAic2hhNTEyIjogInNoYTUxMmhhc2hoZXJlMDE" - "iCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDIKICAg" - "ICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4NzE1NgogI" - "CAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}], " - "\"client_configs\": " - "[ \"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - type_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, _TypeFieldOnSignedTargetsMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgIl9" - "0eXBlIjoge30sCiAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgIm9wYXF1ZV9i" - "YWNrZW5kX3N0YXRlIjogInNvbWV0aGluZyIKICAgICAgICB9LAogICAgICAgICJleHBpc" - "mVzIjogIjIwMjItMTEtMDRUMTM6MzE6NTlaIiwKICAgICAgICAic3BlY192ZXJzaW9uIj" - "ogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2c" - "vMi9BUE1fU0FNUExJTkcvZHluYW1pY19yYXRlcy9jb25maWciOiB7CiAgICAgICAgICAg" - "ICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgICAgICJ2IjogMQogICAgICAgI" - "CAgICAgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgIC" - "AgICAgICAgInNoYTI1NiI6ICJibGFoIiwKICAgICAgICAgICAgICAgICAgICAic2hhNTE" - "yIjogInNoYTUxMmhhc2hoZXJlMDEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAg" - "ICAgICAgImxlbmd0aCI6IDIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgI" - "nZlcnNpb24iOiAyNzQ4NzE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}], " - "\"client_configs\": " - "[ \"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - type_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, _TypeFieldOnSignedTargetsMustBeEqualToTargets) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgIl9" - "0eXBlIjogIm5vbl9hY2NlcHRlZF90eXBlIiwKICAgICAgICAiY3VzdG9tIjogewogICAg" - "ICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAic29tZXRoaW5nIgogICAgICAgI" - "H0sCiAgICAgICAgImV4cGlyZXMiOiAiMjAyMi0xMS0wNFQxMzozMTo1OVoiLAogICAgIC" - "AgICJzcGVjX3ZlcnNpb24iOiAiMS4wLjAiLAogICAgICAgICJ0YXJnZXRzIjogewogICA" - "gICAgICAgICAiZGF0YWRvZy8yL0FQTV9TQU1QTElORy9keW5hbWljX3JhdGVzL2NvbmZp" - "ZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgI" - "CAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImhhc2hlcy" - "I6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImJsYWgiLAogICAgICAgICA" - "gICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcmUwMSIKICAgICAgICAgICAg" - "ICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMgogICAgICAgICAgICB9CiAgI" - "CAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}], " - "\"client_configs\": " - "[ \"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - type_signed_targets_field_invalid_type); -} - -TEST(RemoteConfigParser, VersionFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAi" - "ZXlKMlpYSnphVzl1SWpveExDSnpkR0YwWlNJNmV5Sm1hV3hsWDJoaGMyaGxjeUk2V3lKU" - "0t6SkRWbXRsZEVSellXNXBXa2RKYTBaYVpGSk5UMkZZYTNWek1ERjFlbFExTTNwbmVtbF" - "NUR0UwUFNJc0lrSXdXbU0zVDFJclVsVkxjbmRPYjBWRVdqWTNVWFY1V0VscmEyY3hiMk5" - "IVldSM2VrWnNTMGREWkZVOUlpd2llSEZxVGxVeFRVeFhVM0JSYkRaTmFreFBVMk52U1VK" - "MmIzbFNlbFpyZHpaek5HRXJkWFZ3T1dnd1FUMGlYWDE5IgogICAgICAgIH0sCiAgICAgI" - "CAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2" - "R5bmFtaWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHs" - "KICAgICAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAog" - "ICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhM" - "jU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2OD" - "cwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICA" - "gICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkYXRh" - "ZG9nLzIvREVCVUcvbHVrZS5zdGVlbnNlbi9jb25maWciOiB7CiAgICAgICAgICAgICAgI" - "CAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgICAgICJ2IjogMwogICAgICAgICAgIC" - "AgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICA" - "gICAgInNoYTI1NiI6ICJjNmE4Y2Q1MzUzMGI1OTJhNTA5N2EzMjMyY2U0OWNhMDgwNmZh" - "MzI0NzM1NjRjM2FiMzg2YmViYWVhN2Q4NzQwIgogICAgICAgICAgICAgICAgfSwKICAgI" - "CAgICAgICAgICAgICJsZW5ndGgiOiAxMwogICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AiZW1wbG95ZWUvREVCVUdfREQvMi50ZXN0MS5jb25maWcvY29uZmlnIjogewogICAgICA" - "gICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgICAgICAgICAidiI6IDEKICAg" - "ICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgI" - "CAgICAgICAgICAgICJzaGEyNTYiOiAiNDdlZDgyNTY0N2FkMGVjNmE3ODk5MTg4OTA1Nj" - "VkNDRjMzlhNWU0YmFjZDM1YmIzNGY5ZGYzODMzODkxMmRhZSIKICAgICAgICAgICAgICA" - "gIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgfQogICAg" - "ICAgIH0KICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - version_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, VersionFieldOnSignedTargetsMustBeNumber) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAi" - "ZXlKMlpYSnphVzl1SWpveExDSnpkR0YwWlNJNmV5Sm1hV3hsWDJoaGMyaGxjeUk2V3lKU" - "0t6SkRWbXRsZEVSellXNXBXa2RKYTBaYVpGSk5UMkZZYTNWek1ERjFlbFExTTNwbmVtbF" - "NUR0UwUFNJc0lrSXdXbU0zVDFJclVsVkxjbmRPYjBWRVdqWTNVWFY1V0VscmEyY3hiMk5" - "IVldSM2VrWnNTMGREWkZVOUlpd2llSEZxVGxVeFRVeFhVM0JSYkRaTmFreFBVMk52U1VK" - "MmIzbFNlbFpyZHpaek5HRXJkWFZ3T1dnd1FUMGlYWDE5IgogICAgICAgIH0sCiAgICAgI" - "CAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2" - "R5bmFtaWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHs" - "KICAgICAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAog" - "ICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhM" - "jU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2OD" - "cwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICA" - "gICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkYXRh" - "ZG9nLzIvREVCVUcvbHVrZS5zdGVlbnNlbi9jb25maWciOiB7CiAgICAgICAgICAgICAgI" - "CAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgICAgICJ2IjogMwogICAgICAgICAgIC" - "AgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICA" - "gICAgInNoYTI1NiI6ICJjNmE4Y2Q1MzUzMGI1OTJhNTA5N2EzMjMyY2U0OWNhMDgwNmZh" - "MzI0NzM1NjRjM2FiMzg2YmViYWVhN2Q4NzQwIgogICAgICAgICAgICAgICAgfSwKICAgI" - "CAgICAgICAgICAgICJsZW5ndGgiOiAxMwogICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AiZW1wbG95ZWUvREVCVUdfREQvMi50ZXN0MS5jb25maWcvY29uZmlnIjogewogICAgICA" - "gICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgICAgICAgICAidiI6IDEKICAg" - "ICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgI" - "CAgICAgICAgICAgICJzaGEyNTYiOiAiNDdlZDgyNTY0N2FkMGVjNmE3ODk5MTg4OTA1Nj" - "VkNDRjMzlhNWU0YmFjZDM1YmIzNGY5ZGYzODMzODkxMmRhZSIKICAgICAgICAgICAgICA" - "gIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgfQogICAg" - "ICAgIH0sCiAgICAgICAgInZlcnNpb24iOiB7fQogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - version_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, CustomFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImV4cG" - "lyZXMiOiAiMjAyMi0xMS0wNFQxMzozMTo1OVoiLAogICAgICAgICAgICAgICAgInNwZWN" - "fdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgICAgICAgICAidGFyZ2V0cyI6IHsKICAg" - "ICAgICAgICAgICAgICAgICAgICAgImRhdGFkb2cvMi9GRUFUVVJFUy9keW5hbWljX3Jhd" - "GVzL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG" - "9tIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiA" - "zNjc0MAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiMDc0NjVjZWNlNDdlNDU0MmFiYz" - "BkYTA0MGQ5ZWJiNDJlYzk3MjI0OTIwZDY4NzA2NTFkYzMzMTY1Mjg2MDlkNSIKICAgICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICJsZW5ndGgiOiA2NjM5OQogICAgICAgICAgICAgICAgICAgICAgICB9L" - "AogICAgICAgICAgICAgICAgICAgICAgICAiZGF0YWRvZy8yL0ZFQVRVUkVTL2x1a2Uuc3" - "RlZW5zZW4vY29uZmlnIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ" - "jdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAi" - "diI6IDMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk" - "3YTMyMzJjZTQ5Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiCiAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAibGVuZ3RoIjogMTMKICAgICAgICAgICAgICAgICAgICAgICAgfSwKIC" - "AgICAgICAgICAgICAgICAgICAgICAgImVtcGxveWVlL0ZFQVRVUkVTLzIudGVzdDEuY29" - "uZmlnL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3Vz" - "dG9tIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiO" - "iAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI0N2VkODI1NjQ3YWQwZWM2YTc4OTkx" - "ODg5MDU2NWQ0NGMzOWE1ZTRiYWNkMzViYjM0ZjlkZjM4MzM4OTEyZGFlIgogICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgImxlbmd0aCI6IDQxCiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICA" - "gICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAg" - "ICAgICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, CustomFieldOnSignedTargetsMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3" - "RvbSI6ICJpbnZhbGlkIiwKICAgICAgICAgICAgICAgICJleHBpcmVzIjogIjIwMjItMTE" - "tMDRUMTM6MzE6NTlaIiwKICAgICAgICAgICAgICAgICJzcGVjX3ZlcnNpb24iOiAiMS4w" - "LjAiLAogICAgICAgICAgICAgICAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICAgICAgI" - "CAgICAgICJkYXRhZG9nLzIvRkVBVFVSRVMvZHluYW1pY19yYXRlcy9jb25maWciOiB7Ci" - "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2IjogMzY3NDAKICAgICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAic2hhMjU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM" - "5NzIyNDkyMGQ2ODcwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVuZ" - "3RoIjogNjYzOTkKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgIC" - "AgICAgICAgICAgImRhdGFkb2cvMi9GRUFUVVJFUy9sdWtlLnN0ZWVuc2VuL2NvbmZpZyI" - "6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgICAgInNoYTI1NiI6ICJjNmE4Y2Q1MzUzMGI1OTJhNTA5N2EzMjMyY2U0OWNhMDgw" - "NmZhMzI0NzM1NjRjM2FiMzg2YmViYWVhN2Q4NzQwIgogICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxlbm" - "d0aCI6IDEzCiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICA" - "gICAgICAgICJlbXBsb3llZS9GRUFUVVJFUy8yLnRlc3QxLmNvbmZpZy9jb25maWciOiB7" - "CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2IjogMQogICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICJzaGEyNTYiOiAiNDdlZDgyNTY0N2FkMGVjNmE3ODk5MTg4OTA1NjVkNDRjMzlhN" - "WU0YmFjZDM1YmIzNGY5ZGYzODMzODkxMmRhZSIKICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsZW5ndGg" - "iOiA0MQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9LAog" - "ICAgICAgICAgICAgICAgInZlcnNpb24iOiAyNzQ4NzE1NgogICAgICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, - OpaqueBackendStateCustomFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3" - "RvbSI6IHt9LAogICAgICAgICAgICAgICAgImV4cGlyZXMiOiAiMjAyMi0xMS0wNFQxMzo" - "zMTo1OVoiLAogICAgICAgICAgICAgICAgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAg" - "ICAgICAgICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgI" - "mRhdGFkb2cvMi9GRUFUVVJFUy9keW5hbWljX3JhdGVzL2NvbmZpZyI6IHsKICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAzNjc0MAogICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "mhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "JzaGEyNTYiOiAiMDc0NjVjZWNlNDdlNDU0MmFiYzBkYTA0MGQ5ZWJiNDJlYzk3MjI0OTI" - "wZDY4NzA2NTFkYzMzMTY1Mjg2MDlkNSIKICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsZW5ndGgiOiA2N" - "jM5OQogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgIC" - "AgICAiZGF0YWRvZy8yL0ZFQVRVUkVTL2x1a2Uuc3RlZW5zZW4vY29uZmlnIjogewogICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidiI6IDMKICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "JoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - "ic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5Y2EwODA2ZmEzMjQ3" - "MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVuZ3RoIjogMT" - "MKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICA" - "gImVtcGxveWVlL0ZFQVRVUkVTLzIudGVzdDEuY29uZmlnL2NvbmZpZyI6IHsKICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaGF" - "zaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNo" - "YTI1NiI6ICI0N2VkODI1NjQ3YWQwZWM2YTc4OTkxODg5MDU2NWQ0NGMzOWE1ZTRiYWNkM" - "zViYjM0ZjlkZjM4MzM4OTEyZGFlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxlbmd0aCI6IDQxCiA" - "gICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAg" - "ICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgICAgICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - obs_custom_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, - OpaqueBackendStateCustomFieldOnSignedTargetsMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3" - "RvbSI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXR" - "lIjoge30KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcyI6" - "ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJza" - "W9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZGF0YWRvZy8yL0ZFQVRVUkVTL2R5bmFtaWNfcmF0ZXMvY29" - "uZmlnIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjdXN0b20iOiB7" - "CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidiI6IDM2NzQwC" - "iAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgInNoYTI1NiI6ICIwNzQ2NWNlY2U0N2U0NTQyYWJjMGRhMDQw" - "ZDllYmI0MmVjOTcyMjQ5MjBkNjg3MDY1MWRjMzMxNjUyODYwOWQ1IgogICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICA" - "gICAgICAgICAgICAgICAgICAgICJkYXRhZG9nLzIvRkVBVFVSRVMvbHVrZS5zdGVlbnNl" - "bi9jb25maWciOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImN1c3Rvb" - "SI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2IjogMw" - "ogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiYzZhOGNkNTM1MzBiNTkyYTUwOTdhMzIzM" - "mNlNDljYTA4MDZmYTMyNDczNTY0YzNhYjM4NmJlYmFlYTdkODc0MCIKICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgICJsZW5ndGgiOiAxMwogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAg" - "ICAgICAgICAgICAgICAgICAiZW1wbG95ZWUvRkVBVFVSRVMvMi50ZXN0MS5jb25maWcvY" - "29uZmlnIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjdXN0b20iOi" - "B7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidiI6IDEKICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZWQ4MjU2NDdhZDBlYzZhNzg5OTE4ODkwNT" - "Y1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgzMzg5MTJkYWUiCiAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgI" - "CAgICAgfSwKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgIC" - "B9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - obs_custom_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, TargetsFieldOnSignedTargetsMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiBb" - "XQogICAgICAgIH0sCiAgICAgICAgInRhcmdldHMiOiBbXSwKICAgICAgICAidmVyc2lvb" - "iI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, TargetsFieldOnSignedTargetsMustExists) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiBb" - "XQogICAgICAgIH0sICAgICAgICAKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgI" - "CB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, CustomOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwND" - "BkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICA" - "gICAgICB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAg" - "IH0KICAgICAgICB9LAogICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgIH0KfQ==" - "\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_path_targets_field_missing); -} - -TEST(RemoteConfigParser, CustomOnPathMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiBb" - "XQogICAgICAgIH0sCiAgICAgICAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZ" - "G9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgIC" - "AgICAgICAgImN1c3RvbSI6ICJpbnZhbGlkIiwKICAgICAgICAgICAgICAgICJoYXNoZXM" - "iOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICIwNzQ2NWNlY2U0N2U0NTQy" - "YWJjMGRhMDQwZDllYmI0MmVjOTcyMjQ5MjBkNjg3MDY1MWRjMzMxNjUyODYwOWQ1IgogI" - "CAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiA2NjM5OQogIC" - "AgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiA" - "gICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, VCustomOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgIC" - "AgICAgICAgICAgICJzaGEyNTYiOiAiMDc0NjVjZWNlNDdlNDU0MmFiYzBkYTA0MGQ5ZWJ" - "iNDJlYzk3MjI0OTIwZDY4NzA2NTFkYzMzMTY1Mjg2MDlkNSIKICAgICAgICAgICAgICAg" - "IH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfQogI" - "CAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4NzE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - v_path_targets_field_missing); -} - -TEST(RemoteConfigParser, VCustomOnPathMustBeNumber) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6ICJpbnZhbGlkIgogICAgICAgICAgICAgICAgfSwKIC" - "AgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI" - "1NiI6ICIwNzQ2NWNlY2U0N2U0NTQyYWJjMGRhMDQwZDllYmI0MmVjOTcyMjQ5MjBkNjg3" - "MDY1MWRjMzMxNjUyODYwOWQ1IgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgI" - "CAgICJsZW5ndGgiOiA2NjM5OQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgIC" - "AidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - v_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, HashesOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgIH0KICAgICAgICB9LAo" - "gICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hashes_path_targets_field_missing); -} - -TEST(RemoteConfigParser, HashesOnPathMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6ICJpbnZhbGlkIiwKICAgICAgICAgICAgICAgICJsZW5" - "ndGgiOiA2NjM5OQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lv" - "biI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hashes_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, AtLeastOneHashMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICA" - "gICAgICAibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAg" - "ICAgInZlcnNpb24iOiAyNzQ4NzE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hashes_path_targets_field_empty); -} - -TEST(RemoteConfigParser, HashesOnPathMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2Ijo" - "ge30KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNjYz" - "OTkKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4N" - "zE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hash_hashes_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, LengthOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2Ijo" - "gIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUx" - "ZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgI" - "CAgICB9LAogICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - length_path_targets_field_missing); -} - -TEST(RemoteConfigParser, LengthOnPathMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2Ijo" - "gIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUx" - "ZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgI" - "mxlbmd0aCI6ICJpbnZhbGlkIgogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgIC" - "AidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - length_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, TargetsAreParsed) -{ - std::string response = get_example_response(); - - auto gcr = remote_config::protocol::parse(response); - - std::optional _targets = gcr.targets; - - EXPECT_EQ(27487156, _targets->version); - - std::unordered_map paths = - _targets->paths; - - EXPECT_EQ(3, paths.size()); - - auto path_itr = paths.find("datadog/2/APM_SAMPLING/dynamic_rates/config"); - auto temp_path = path_itr->second; - EXPECT_EQ(36740, temp_path.custom_v); - EXPECT_EQ(2, temp_path.hashes.size()); - EXPECT_EQ( - "07465cece47e4542abc0da040d9ebb42ec97224920d6870651dc3316528609d5", - temp_path.hashes["sha256"]); - EXPECT_EQ("sha512hashhere01", temp_path.hashes["sha512"]); - EXPECT_EQ(66399, temp_path.length); - - path_itr = paths.find("datadog/2/DEBUG/luke.steensen/config"); - temp_path = path_itr->second; - EXPECT_EQ(3, temp_path.custom_v); - EXPECT_EQ(2, temp_path.hashes.size()); - EXPECT_EQ( - "c6a8cd53530b592a5097a3232ce49ca0806fa32473564c3ab386bebaea7d8740", - temp_path.hashes["sha256"]); - EXPECT_EQ("sha512hashhere02", temp_path.hashes["sha512"]); - EXPECT_EQ(13, temp_path.length); - - path_itr = paths.find("employee/DEBUG_DD/2.test1.config/config"); - temp_path = path_itr->second; - EXPECT_EQ(1, temp_path.custom_v); - EXPECT_EQ(2, temp_path.hashes.size()); - EXPECT_EQ( - "47ed825647ad0ec6a789918890565d44c39a5e4bacd35bb34f9df38338912dae", - temp_path.hashes["sha256"]); - EXPECT_EQ("sha512hashhere03", temp_path.hashes["sha512"]); - EXPECT_EQ(41, temp_path.length); -} - -TEST(RemoteConfigParser, RemoteConfigParserResultCanBeCastToString) -{ - EXPECT_EQ("success", - remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result::success)); - EXPECT_EQ("target_files_path_field_invalid_type", - remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result:: - target_files_path_field_invalid_type)); - EXPECT_EQ("length_path_targets_field_missing", - remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result:: - length_path_targets_field_missing)); - EXPECT_EQ("", remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result:: - num_of_values)); -} - -TEST(RemoteConfigParser, ParseEmptyResponses) -{ - auto gcr = remote_config::protocol::parse("{}"); - - EXPECT_FALSE(gcr.targets.has_value()); -} - -TEST(RemoteConfigParser, ParseInfoResponse) -{ - const std::string response = - R"({"endpoints": ["/some/endpoint", "/another/endpoint"] })"; - auto info_response = remote_config::protocol::parse_info(response); - - EXPECT_EQ(2, info_response.endpoints.size()); - EXPECT_TRUE(std::find(info_response.endpoints.begin(), - info_response.endpoints.end(), - "/some/endpoint") != info_response.endpoints.end()); - EXPECT_TRUE(std::find(info_response.endpoints.begin(), - info_response.endpoints.end(), - "/another/endpoint") != info_response.endpoints.end()); -} - -TEST(RemoteConfigParser, ParseInfoInvalidJsonThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, "invalid_json", - remote_config::protocol::remote_config_parser_result::invalid_json); -} - -TEST(RemoteConfigParser, ParseInfoNonObjectPayloadThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, "[]", - remote_config::protocol::remote_config_parser_result::invalid_response); -} - -TEST(RemoteConfigParser, ParseInfoWithoutEndpointsThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, - R"({"some":"value"})", - remote_config::protocol::remote_config_parser_result:: - endpoints_field_missing); -} - -TEST(RemoteConfigParser, ParseInfoWithEndpointsNotArrayThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, - R"({"endpoints":"invalid_type"})", - remote_config::protocol::remote_config_parser_result:: - endpoints_field_invalid); -} - -TEST(RemoteConfigParser, ParseInfoWithNonStringEndpointThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, - R"({"endpoints": [ 1234 ]})", - remote_config::protocol::remote_config_parser_result::invalid_endpoint); -} - -} // namespace dds diff --git a/appsec/tests/helper/remote_config/product_test.cpp b/appsec/tests/helper/remote_config/product_test.cpp deleted file mode 100644 index 997cc403cc..0000000000 --- a/appsec/tests/helper/remote_config/product_test.cpp +++ /dev/null @@ -1,262 +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.hpp" -#include "remote_config/config.hpp" -#include "remote_config/exception.hpp" -#include "remote_config/listeners/listener.hpp" -#include "remote_config/product.hpp" - -using capabilities_e = dds::remote_config::protocol::capabilities_e; - -namespace dds { - -namespace mock { - -ACTION(ThrowErrorApplyingConfig) -{ - throw remote_config::error_applying_config("some error"); -}; - -class listener_mock : public remote_config::listener_base { -public: - listener_mock(std::string name = "MOCK_PRODUCT", - remote_config::protocol::capabilities_e capability = - remote_config::protocol::capabilities_e::ASM_DD_RULES) - : name_(name), capability_(capability){}; - ~listener_mock() override = default; - - MOCK_METHOD( - void, on_update, ((const remote_config::config &config)), (override)); - MOCK_METHOD( - void, on_unapply, ((const remote_config::config &config)), (override)); - MOCK_METHOD(void, init, (), (override)); - MOCK_METHOD(void, commit, (), (override)); - - [[nodiscard]] std::unordered_map - get_supported_products() override - { - return {{name_, capability_}}; - } - -protected: - std::string name_; - remote_config::protocol::capabilities_e capability_; -}; -} // namespace mock - -remote_config::config get_config(std::string id) -{ - return {"some product", id, "some contents", "some path", {}, 123, 321, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; -} - -remote_config::config get_config() { return get_config("some id"); } - -remote_config::config unacknowledged(remote_config::config c) -{ - c.apply_state = - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED; - return c; -} - -remote_config::config acknowledged(remote_config::config c) -{ - c.apply_state = - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED; - return c; -} - -TEST(RemoteConfigProduct, InvalidListener) -{ - EXPECT_THROW(remote_config::product("", nullptr), std::runtime_error); -} - -TEST(RemoteConfigProduct, NameFromListenerIsSaved) -{ - auto listener = std::make_shared(); - remote_config::product product("MOCK_PRODUCT", listener); - - EXPECT_EQ("MOCK_PRODUCT", product.get_name()); -} - -TEST(RemoteConfigProduct, ConfigsAreEmptyByDefault) -{ - auto listener = std::make_shared(); - remote_config::product product("MOCK_PRODUCT", listener); - - EXPECT_EQ(0, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, ConfigsAreSaved) -{ - auto listener = std::make_shared(); - remote_config::product product("MOCK_PRODUCT", listener); - - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(config)).Times(1); - - product.assign_configs({{"config name", config}}); - - auto configs_on_product = product.get_configs(); - auto config_saved = configs_on_product.find("config name"); - - EXPECT_EQ(1, configs_on_product.size()); - EXPECT_EQ("config name", config_saved->first); - EXPECT_EQ(acknowledged(config), config_saved->second); -} - -TEST( - RemoteConfigProduct, WhenAConfigIsSavedTheProductListenerIsCalledToOnUpdate) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - EXPECT_CALL(*listener, on_update(config)).Times(1); - EXPECT_CALL(*listener, on_unapply(_)).Times(0); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", config}}); - - EXPECT_EQ( - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED, - product.get_configs().find("config name")->second.apply_state); -} - -TEST(RemoteConfigProduct, - WhenAConfigIsRemovedTheProductListenerIsCalledToUnApply) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(unacknowledged(config))).Times(1); - EXPECT_CALL(*listener, on_unapply(acknowledged(config))).Times(1); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", unacknowledged(config)}}); - product.assign_configs({}); - - EXPECT_EQ(0, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenConfigDoesNotChangeItsListenersShouldNotBeCalled) -{ - auto listener = std::make_shared(); - remote_config::config config01 = get_config("id 01"); - remote_config::config config02 = get_config("id 02"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config02))).Times(1); - - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - - EXPECT_EQ(2, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, EvenIfJustOneKeyConfigIsDiferentItCallsToAllListeners) -{ - auto listener = std::make_shared(); - remote_config::config config01 = get_config("id 01"); - remote_config::config config02 = get_config("id 02"); - remote_config::config config03 = get_config("id 03"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(acknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config02))).Times(1); - EXPECT_CALL(*listener, on_update(acknowledged(config02))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config03))).Times(1); - - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}, - {"config name 03", unacknowledged(config03)}}); - - EXPECT_EQ(3, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenAConfigGetsDeletedItAlsoUpdateWaf) -{ - auto listener = std::make_shared(); - remote_config::config config01 = get_config("id 01"); - remote_config::config config02 = get_config("id 02"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(acknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config02))).Times(1); - EXPECT_CALL(*listener, on_unapply(acknowledged(config02))).Times(1); - - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - product.assign_configs({{"config name 01", unacknowledged(config01)}}); - - EXPECT_EQ(1, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenAConfigChangeItsHashItsListenerUpdateIsCalled) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - remote_config::config same_config_different_hash = get_config(); - same_config_different_hash.hashes.emplace("hash key", "hash value"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config))).Times(1); - EXPECT_CALL( - *listener, on_update(unacknowledged(same_config_different_hash))) - .Times(1); - EXPECT_CALL(*listener, on_unapply(_)).Times(0); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", config}}); - product.assign_configs({{"config name", same_config_different_hash}}); - - EXPECT_EQ(1, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, SameConfigWithDifferentNameItsTreatedAsNewConfig) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(unacknowledged(config))).Times(2); - EXPECT_CALL(*listener, on_unapply(acknowledged(config))).Times(1); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", config}}); - product.assign_configs({{"config name 02", config}}); - - EXPECT_EQ(1, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenAListenerFailsUpdatingAConfigItsStateGetsError) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(_)) - .WillRepeatedly(mock::ThrowErrorApplyingConfig()); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", config}}); - - EXPECT_EQ(1, product.get_configs().size()); - EXPECT_EQ(remote_config::protocol::config_state::applied_state::ERROR, - product.get_configs().find("config name")->second.apply_state); - EXPECT_EQ("some error", - product.get_configs().find("config name")->second.apply_error); -} - -} // namespace dds diff --git a/appsec/tests/helper/remote_config/serializer_test.cpp b/appsec/tests/helper/remote_config/serializer_test.cpp deleted file mode 100644 index 7ed8d0f3a5..0000000000 --- a/appsec/tests/helper/remote_config/serializer_test.cpp +++ /dev/null @@ -1,351 +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 "../common.hpp" -#include "base64.h" -#include "remote_config/protocol/client.hpp" -#include "remote_config/protocol/client_state.hpp" -#include "remote_config/protocol/client_tracer.hpp" -#include "remote_config/protocol/config_state.hpp" -#include "remote_config/protocol/tuf/get_configs_request.hpp" -#include "remote_config/protocol/tuf/serializer.hpp" - -namespace dds { - -bool array_contains_string(const rapidjson::Value &array, const char *searched) -{ - if (!array.IsArray()) { - return false; - } - - bool result = false; - for (rapidjson::Value::ConstValueIterator itr = array.Begin(); - itr != array.End(); ++itr) { - if (itr->IsString() && strcmp(searched, itr->GetString()) == 0) { - result = true; - } - } - - return result; -} - -rapidjson::Value::ConstMemberIterator assert_it_contains_string( - const rapidjson::Value &parent_field, const char *key, const char *value) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - EXPECT_EQ(rapidjson::kStringType, tmp_itr->value.GetType()); - EXPECT_EQ(value, tmp_itr->value); - - return tmp_itr; -} - -rapidjson::Value::ConstMemberIterator assert_it_contains_int( - const rapidjson::Value &parent_field, const char *key, int value) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - EXPECT_EQ(rapidjson::kNumberType, tmp_itr->value.GetType()); - EXPECT_EQ(value, tmp_itr->value); - - return tmp_itr; -} - -rapidjson::Value::ConstMemberIterator assert_it_contains_bool( - const rapidjson::Value &parent_field, const char *key, bool value) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - rapidjson::Type type = rapidjson::kTrueType; - if (!value) { - type = rapidjson::kFalseType; - } - EXPECT_EQ(type, tmp_itr->value.GetType()); - EXPECT_EQ(value, tmp_itr->value); - - return tmp_itr; -} - -rapidjson::Value::ConstMemberIterator find_and_assert_type( - const rapidjson::Value &parent_field, const char *key, rapidjson::Type type) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - EXPECT_EQ(type, tmp_itr->value.GetType()) - << "Key " << key << " not matching expected type"; - - return tmp_itr; -} - -int config_state_version = 456; -int targets_version = 123; - -remote_config::protocol::client get_client() -{ - remote_config::protocol::client_tracer client_tracer = {"some runtime id", - "some tracer version", "some service", {"extra01", "extra02"}, - "some env", "some app version"}; - - std::vector config_states; - - remote_config::protocol::config_state cs_unknown = { - "unknown config_state id", 11, "unknown config_state product", - remote_config::protocol::config_state::applied_state::UNKNOWN, ""}; - remote_config::protocol::config_state cs_unacknowledged = { - "unacknowledged config_state id", 22, - "unacknowledged config_state product", - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - remote_config::protocol::config_state cs_acknowledged = { - "acknowledged config_state id", 33, "acknowledged config_state product", - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED, ""}; - remote_config::protocol::config_state cs_error = {"error config_state id", - 44, "error config_state product", - remote_config::protocol::config_state::applied_state::ERROR, - "error description"}; - config_states.push_back(cs_unknown); - config_states.push_back(cs_unacknowledged); - config_states.push_back(cs_acknowledged); - config_states.push_back(cs_error); - - remote_config::protocol::client_state client_s = { - targets_version, config_states, false, "", "some backend client state"}; - - return {"some_id", {"ASM_DD"}, client_tracer, client_s}; -} - -std::vector -get_cached_target_files() -{ - std::vector - cached_target_files; - - std::vector first_hashes; - remote_config::protocol::cached_target_files_hash first_hash{ - "first hash algorithm", "first hash hash"}; - first_hashes.push_back(first_hash); - remote_config::protocol::cached_target_files first{ - "first some path", 1, std::move(first_hashes)}; - cached_target_files.push_back(first); - - std::vector - second_hashes; - remote_config::protocol::cached_target_files_hash second_hash{ - "second hash algorithm", "second hash hash"}; - second_hashes.push_back(second_hash); - remote_config::protocol::cached_target_files second{ - "second some path", 1, std::move(second_hashes)}; - cached_target_files.push_back(second); - - return cached_target_files; -} - -TEST(RemoteConfigSerializer, RequestCanBeSerializedWithClientField) -{ - remote_config::protocol::get_configs_request request = { - get_client(), get_cached_target_files()}; - - std::optional serialised_string; - serialised_string = remote_config::protocol::serialize(std::move(request)); - - EXPECT_TRUE(serialised_string); - - // Lets transform the resulting string back to json so we can assert more - // easily - rapidjson::Document serialized_doc; - serialized_doc.Parse(serialised_string.value()); - - // Client fields - rapidjson::Value::ConstMemberIterator client_itr = - find_and_assert_type(serialized_doc, "client", rapidjson::kObjectType); - - assert_it_contains_string(client_itr->value, "id", "some_id"); - assert_it_contains_bool(client_itr->value, "is_tracer", true); - - // Client products fields - rapidjson::Value::ConstMemberIterator products_itr = find_and_assert_type( - client_itr->value, "products", rapidjson::kArrayType); - array_contains_string(products_itr->value, "ASM_DD"); - - // Client tracer fields - rapidjson::Value::ConstMemberIterator client_tracer_itr = - find_and_assert_type( - client_itr->value, "client_tracer", rapidjson::kObjectType); - assert_it_contains_string(client_tracer_itr->value, "language", "php"); - assert_it_contains_string( - client_tracer_itr->value, "runtime_id", "some runtime id"); - assert_it_contains_string( - client_tracer_itr->value, "tracer_version", "some tracer version"); - assert_it_contains_string( - client_tracer_itr->value, "service", "some service"); - assert_it_contains_string(client_tracer_itr->value, "env", "some env"); - assert_it_contains_string( - client_tracer_itr->value, "app_version", "some app version"); - - rapidjson::Value::ConstMemberIterator extra_services_itr = - find_and_assert_type( - client_tracer_itr->value, "extra_services", rapidjson::kArrayType); - EXPECT_EQ("extra01", extra_services_itr->value[0]); - EXPECT_EQ("extra02", extra_services_itr->value[1]); - - // Client state fields - rapidjson::Value::ConstMemberIterator client_state_itr = - find_and_assert_type( - client_itr->value, "state", rapidjson::kObjectType); - assert_it_contains_int( - client_state_itr->value, "targets_version", targets_version); - assert_it_contains_int(client_state_itr->value, "root_version", 1); - assert_it_contains_bool(client_state_itr->value, "has_error", false); - assert_it_contains_string(client_state_itr->value, "error", ""); - assert_it_contains_string(client_state_itr->value, "backend_client_state", - "some backend client state"); - - // Config state fields - rapidjson::Value::ConstMemberIterator config_states_itr = - find_and_assert_type( - client_state_itr->value, "config_states", rapidjson::kArrayType); - ; - - // UNKNOWN - rapidjson::Value::ConstValueIterator itr = config_states_itr->value.Begin(); - assert_it_contains_string(*itr, "id", "unknown config_state id"); - assert_it_contains_int(*itr, "version", 11); - assert_it_contains_string(*itr, "product", "unknown config_state product"); - assert_it_contains_int(*itr, "apply_state", 0); - assert_it_contains_string(*itr, "apply_error", ""); - // UNACKNOWLEDGED - itr++; - assert_it_contains_string(*itr, "id", "unacknowledged config_state id"); - assert_it_contains_int(*itr, "version", 22); - assert_it_contains_string( - *itr, "product", "unacknowledged config_state product"); - assert_it_contains_int(*itr, "apply_state", 1); - assert_it_contains_string(*itr, "apply_error", ""); - // ACKNOWLEDGED - itr++; - assert_it_contains_string(*itr, "id", "acknowledged config_state id"); - assert_it_contains_int(*itr, "version", 33); - assert_it_contains_string( - *itr, "product", "acknowledged config_state product"); - assert_it_contains_int(*itr, "apply_state", 2); - assert_it_contains_string(*itr, "apply_error", ""); - // ERROR - itr++; - assert_it_contains_string(*itr, "id", "error config_state id"); - assert_it_contains_int(*itr, "version", 44); - assert_it_contains_string(*itr, "product", "error config_state product"); - assert_it_contains_int(*itr, "apply_state", 3); - assert_it_contains_string(*itr, "apply_error", "error description"); -} - -TEST(RemoteConfigSerializer, RequestCanBeSerializedWithCachedTargetFields) -{ - remote_config::protocol::get_configs_request request = { - get_client(), get_cached_target_files()}; - - std::optional serialised_string; - serialised_string = remote_config::protocol::serialize(std::move(request)); - - EXPECT_TRUE(serialised_string); - - // Lets transform the resulting string back to json so we can assert more - // easily - rapidjson::Document serialized_doc; - serialized_doc.Parse(serialised_string.value()); - - // cached_target_files fields - rapidjson::Value::ConstMemberIterator cached_target_files_itr = - find_and_assert_type( - serialized_doc, "cached_target_files", rapidjson::kArrayType); - - EXPECT_EQ(2, cached_target_files_itr->value.Size()); - - rapidjson::Value::ConstValueIterator first = - cached_target_files_itr->value.Begin(); - assert_it_contains_string(*first, "path", "first some path"); - assert_it_contains_int(*first, "length", 1); - - // Cached target file hash of first - rapidjson::Value::ConstMemberIterator first_cached_target_files_hash = - find_and_assert_type(*first, "hashes", rapidjson::kArrayType); - EXPECT_EQ(1, first_cached_target_files_hash->value.Size()); - assert_it_contains_string(*first_cached_target_files_hash->value.Begin(), - "algorithm", "first hash algorithm"); - assert_it_contains_string(*first_cached_target_files_hash->value.Begin(), - "hash", "first hash hash"); - - rapidjson::Value::ConstValueIterator second = - std::next(cached_target_files_itr->value.Begin()); - assert_it_contains_string(*second, "path", "second some path"); - assert_it_contains_int(*second, "length", 1); - - // Cached target file hash of second - rapidjson::Value::ConstMemberIterator second_cached_target_files_hash = - find_and_assert_type(*second, "hashes", rapidjson::kArrayType); - EXPECT_EQ(1, second_cached_target_files_hash->value.Size()); - assert_it_contains_string(*second_cached_target_files_hash->value.Begin(), - "algorithm", "second hash algorithm"); - assert_it_contains_string(*second_cached_target_files_hash->value.Begin(), - "hash", "second hash hash"); -} - -TEST(RemoteConfigSerializer, CapabilitiesCanBeSet) -{ - auto client = get_client(); - client.capabilities = - remote_config::protocol::capabilities_e::RESERVED | - remote_config::protocol::capabilities_e::ASM_ACTIVATION | - remote_config::protocol::capabilities_e::ASM_IP_BLOCKING | - remote_config::protocol::capabilities_e::ASM_DD_RULES; - - remote_config::protocol::get_configs_request request = { - client, get_cached_target_files()}; - - std::optional serialised_string; - serialised_string = remote_config::protocol::serialize(std::move(request)); - - EXPECT_TRUE(serialised_string); - - // Lets transform the resulting string back to json so we can assert more - // easily - rapidjson::Document serialized_doc; - serialized_doc.Parse(serialised_string.value()); - - // Client fields - rapidjson::Value::ConstMemberIterator client_itr = - find_and_assert_type(serialized_doc, "client", rapidjson::kObjectType); - - rapidjson::Value::ConstMemberIterator capabilities_itr = - find_and_assert_type( - client_itr->value, "capabilities", rapidjson::kStringType); - - EXPECT_STREQ("AA8=", capabilities_itr->value.GetString()); -} - -} // namespace dds diff --git a/appsec/tests/helper/service_manager_test.cpp b/appsec/tests/helper/service_manager_test.cpp index 9fe5a9273e..bd1995dc85 100644 --- a/appsec/tests/helper/service_manager_test.cpp +++ b/appsec/tests/helper/service_manager_test.cpp @@ -33,21 +33,17 @@ TEST(ServiceManagerTest, LoadRulesOK) dds::engine_settings engine_settings; engine_settings.rules_file = fn; engine_settings.waf_timeout_us = 42; - auto service = manager.create_service({"service", {}, "env", "", "", ""}, - engine_settings, {}, meta, metrics, {}); + auto service = + manager.create_service(engine_settings, {}, meta, metrics, {}); + auto *service_rp = service.get(); EXPECT_EQ(manager.get_cache().size(), 1); EXPECT_EQ(metrics[tag::event_rules_loaded], 4); // loading again should take from the cache - auto service2 = manager.create_service({"service", {}, "env", "", "", ""}, - engine_settings, {}, meta, metrics, {}); - EXPECT_EQ(manager.get_cache().size(), 1); - - // Even with different extra services, it should get the same - auto service3 = manager.create_service( - {"service", {"some", "services"}, "env", "", "", ""}, engine_settings, - {}, meta, metrics, {}); + auto service2 = + manager.create_service(engine_settings, {}, meta, metrics, {}); EXPECT_EQ(manager.get_cache().size(), 1); + EXPECT_EQ(service, service2); // destroying the services should expire the cache ptr auto cache_it = manager.get_cache().begin(); @@ -58,20 +54,19 @@ TEST(ServiceManagerTest, LoadRulesOK) service.reset(); ASSERT_FALSE(weak_ptr.expired()); service2.reset(); - ASSERT_FALSE(weak_ptr.expired()); - service3.reset(); // the last one should be kept by the manager ASSERT_FALSE(weak_ptr.expired()); // loading another service should cleanup the cache - auto service4 = manager.create_service( - {"service2", {}, "env"}, engine_settings, {}, meta, metrics, {}); + auto service3 = + manager.create_service(engine_settings, {true}, meta, metrics, {}); + ASSERT_NE(service3.get(), service_rp); ASSERT_TRUE(weak_ptr.expired()); EXPECT_EQ(manager.get_cache().size(), 1); // another service identifier should result in another service - auto service5 = manager.create_service({"service", {}, "env", "", "", ""}, - engine_settings, {}, meta, metrics, {}); + auto service4 = + manager.create_service(engine_settings, {}, meta, metrics, {}); EXPECT_EQ(manager.get_cache().size(), 2); } @@ -86,8 +81,7 @@ TEST(ServiceManagerTest, LoadRulesFileNotFound) dds::engine_settings engine_settings; engine_settings.rules_file = "/file/that/does/not/exist"; engine_settings.waf_timeout_us = 42; - manager.create_service( - {"s", {}, "e"}, engine_settings, {}, meta, metrics, {}); + manager.create_service(engine_settings, {}, meta, metrics, {}); }, std::runtime_error); } @@ -103,8 +97,7 @@ TEST(ServiceManagerTest, BadRulesFile) dds::engine_settings engine_settings; engine_settings.rules_file = "/dev/null"; engine_settings.waf_timeout_us = 42; - manager.create_service( - {"s", {}, "e"}, engine_settings, {}, meta, metrics, {}); + manager.create_service(engine_settings, {}, meta, metrics, {}); }, dds::parsing_error); } @@ -119,12 +112,10 @@ TEST(ServiceManagerTest, UniqueServices) dds::engine_settings engine_settings; engine_settings.rules_file = fn; - auto service1 = manager.create_service( - {"service", {}, "env", "1.0", "2.0", "runtime ID 0"}, engine_settings, - {}, meta, metrics, {}); - auto service2 = manager.create_service( - {"service", {}, "env", "1.1", "3.0", "runtime ID 1"}, engine_settings, - {}, meta, metrics, {}); + auto service1 = + manager.create_service(engine_settings, {}, meta, metrics, {}); + auto service2 = + manager.create_service(engine_settings, {}, meta, metrics, {}); EXPECT_EQ(service1.get(), service2.get()); } diff --git a/appsec/tests/helper/service_test.cpp b/appsec/tests/helper/service_test.cpp index 9fd6bfc340..815fa0d03a 100644 --- a/appsec/tests/helper/service_test.cpp +++ b/appsec/tests/helper/service_test.cpp @@ -5,26 +5,62 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "common.hpp" #include "remote_config/mocks.hpp" +#include "remote_config/settings.hpp" #include #include #include +extern "C" { +struct ddog_CharSlice { + const char *ptr; + uintptr_t len; +}; +struct ddog_RemoteConfigReader { + std::string shm_path; + struct ddog_CharSlice next_line; +}; +__attribute__((visibility("default"))) ddog_RemoteConfigReader * +ddog_remote_config_reader_for_path(const char *path) +{ + return new ddog_RemoteConfigReader{path}; +} +__attribute__((visibility("default"))) bool ddog_remote_config_read( + ddog_RemoteConfigReader *reader, ddog_CharSlice *data) +{ + if (reader->next_line.len == 0) { + return false; + } + data->ptr = reader->next_line.ptr; + data->len = reader->next_line.len; + reader->next_line.len = 0; + return true; +} +__attribute__((visibility("default"))) void ddog_remote_config_reader_drop( + struct ddog_RemoteConfigReader *reader) +{ + delete reader; +} + +__attribute__((constructor)) void resolve_symbols() +{ + dds::remote_config::resolve_symbols(); +} +} + namespace dds { TEST(ServiceTest, NullEngine) { - service_identifier sid{"service", {"extra01", "extra02"}, "env", - "tracer_version", "app_version", "runtime_id"}; - std::shared_ptr engine; - auto client = std::make_unique(sid); - EXPECT_CALL(*client, poll).Times(0); + std::shared_ptr engine{}; + remote_config::settings rc_settings{true, ""}; + auto client = remote_config::client::from_settings(rc_settings, {}); auto service_config = std::make_shared(); - auto client_handler = std::make_shared( - std::move(client), service_config, 1s); + auto client_handler = std::make_unique( + std::move(client), service_config); EXPECT_THROW( - auto s = service(engine, service_config, std::move(client_handler)), + auto s = service(engine, service_config, std::move(client_handler), ""), std::runtime_error); } @@ -35,7 +71,7 @@ TEST(ServiceTest, NullServiceHandler) // A null service handler doesn't make a difference as remote config is // optional - service svc{engine, service_config, nullptr}; + service svc{engine, service_config, nullptr, ""}; EXPECT_EQ(engine.get(), svc.get_engine().get()); } @@ -43,9 +79,7 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { std::shared_ptr engine{engine::create()}; - service_identifier sid{"service", {"extra01", "extra02"}, "env", - "tracer_version", "app_version", "runtime_id"}; - auto client = std::make_unique(sid); + auto client = remote_config::client::from_settings({true}, {}); auto service_config = std::make_shared(); engine_settings engine_settings = {}; engine_settings.rules_file = create_sample_rules_ok(); @@ -54,8 +88,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Constructor. It picks based on rate double all_requests_are_picked = 1.0; - auto s = service( - engine, service_config, nullptr, {true, all_requests_are_picked}); + auto s = service(engine, service_config, nullptr, "", + {true, all_requests_are_picked}); EXPECT_TRUE(s.get_schema_sampler()->picked()); } @@ -63,7 +97,7 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Constructor. It does not pick based on rate double no_request_is_picked = 0.0; auto s = service( - engine, service_config, nullptr, {true, no_request_is_picked}); + engine, service_config, nullptr, "", {true, no_request_is_picked}); EXPECT_FALSE(s.get_schema_sampler()->picked()); } @@ -71,7 +105,7 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Constructor. It does not pick if disabled double all_requests_are_picked = 1.0; bool schema_extraction_disabled = false; - auto s = service(engine, service_config, nullptr, + auto s = service(engine, service_config, nullptr, "", {schema_extraction_disabled, all_requests_are_picked}); EXPECT_FALSE(s.get_schema_sampler()->picked()); @@ -80,8 +114,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Static constructor. It picks based on rate engine_settings.schema_extraction.enabled = true; engine_settings.schema_extraction.sample_rate = 1.0; - auto service = service::from_settings( - service_identifier(sid), engine_settings, {}, meta, metrics, false); + auto service = + service::from_settings(engine_settings, {}, meta, metrics, false); EXPECT_TRUE(service->get_schema_sampler()->picked()); } @@ -89,8 +123,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Static constructor. It does not pick based on rate engine_settings.schema_extraction.enabled = true; engine_settings.schema_extraction.sample_rate = 0.0; - auto service = service::from_settings( - service_identifier(sid), engine_settings, {}, meta, metrics, false); + auto service = + service::from_settings(engine_settings, {}, meta, metrics, false); EXPECT_FALSE(service->get_schema_sampler()->picked()); } @@ -98,8 +132,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Static constructor. It does not pick if disabled engine_settings.schema_extraction.enabled = false; engine_settings.schema_extraction.sample_rate = 1.0; - auto service = service::from_settings( - service_identifier(sid), engine_settings, {}, meta, metrics, false); + auto service = + service::from_settings(engine_settings, {}, meta, metrics, false); EXPECT_FALSE(service->get_schema_sampler()->picked()); } diff --git a/appsec/tests/helper/waf_test.cpp b/appsec/tests/helper/waf_test.cpp index 576d70de3c..0c3d533ce5 100644 --- a/appsec/tests/helper/waf_test.cpp +++ b/appsec/tests/helper/waf_test.cpp @@ -44,7 +44,7 @@ TEST(WafTest, InitWithInvalidRules) std::map meta; std::map metrics; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_settings(cs, ruleset, meta, metrics)}; EXPECT_EQ(meta.size(), 2); @@ -70,7 +70,8 @@ TEST(WafTest, RunWithInvalidParam) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); parameter_view pv; dds::event e; @@ -82,7 +83,8 @@ TEST(WafTest, RunWithTimeout) std::map meta; std::map metrics; - subscriber::ptr wi(waf::instance::from_string(waf_rule, meta, metrics, 0)); + std::shared_ptr wi( + waf::instance::from_string(waf_rule, meta, metrics, 0)); auto ctx = wi->get_listener(); auto p = parameter::map(); @@ -99,7 +101,8 @@ TEST(WafTest, ValidRunGood) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); auto p = parameter::map(); @@ -119,7 +122,8 @@ TEST(WafTest, ValidRunMonitor) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); auto p = parameter::map(); @@ -148,8 +152,9 @@ TEST(WafTest, ValidRunMonitorObfuscated) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics, - waf::instance::default_waf_timeout_us, "password"sv, "string 3"sv)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics, + waf::instance::default_waf_timeout_us, "password"sv, "string 3"sv)}; auto ctx = wi->get_listener(); auto p = parameter::map(), sub_p = parameter::map(); @@ -189,7 +194,7 @@ TEST(WafTest, ValidRunMonitorObfuscatedFromSettings) cs.obfuscator_key_regex = "password"; auto ruleset = engine_ruleset::from_path(cs.rules_file); - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_settings(cs, ruleset, meta, metrics)}; auto ctx = wi->get_listener(); @@ -223,7 +228,7 @@ TEST(WafTest, UpdateRuleData) std::map meta; std::map metrics; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(waf_rule_with_data, meta, metrics)}; ASSERT_TRUE(wi); @@ -282,7 +287,7 @@ TEST(WafTest, UpdateInvalid) std::map meta; std::map metrics; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(waf_rule_with_data, meta, metrics)}; ASSERT_TRUE(wi); @@ -307,7 +312,8 @@ TEST(WafTest, SchemasAreAdded) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); auto p = parameter::map(), sub_p = parameter::map(); @@ -343,7 +349,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["custom"]}],"actions":[{"id":"custom","type":"block_request","parameters":{"status_code":123,"grpc_status_code":321,"type":"json","custom_param":"foo"}}],"rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); @@ -382,7 +388,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["custom"]}],"actions":[{"id":"custom","type":"block_request","parameters":{}}],"rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); @@ -421,7 +427,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["custom"]}],"actions":[{"id":"custom","type":"custom_type","parameters":{"some":"parameter"}}],"rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); @@ -455,7 +461,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["block"]}], "rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); diff --git a/appsec/tests/integration/build.gradle b/appsec/tests/integration/build.gradle index 4103c09868..2402f703be 100644 --- a/appsec/tests/integration/build.gradle +++ b/appsec/tests/integration/build.gradle @@ -286,6 +286,7 @@ def buildTracerTask = { String version, String variant -> '../../../ext', '../../../zend_abstract_interface', '../../../libdatadog', + '../../../ddtrace.sym', ], ], outputs: [ diff --git a/appsec/tests/integration/src/docker/php/Dockerfile-php-deps b/appsec/tests/integration/src/docker/php/Dockerfile-php-deps index 82bbd360e7..dcbfec8ddd 100644 --- a/appsec/tests/integration/src/docker/php/Dockerfile-php-deps +++ b/appsec/tests/integration/src/docker/php/Dockerfile-php-deps @@ -17,9 +17,10 @@ RUN apt-get update && apt-get install -y \ python3-dev \ vim \ && rm -rf /var/lib/apt/lists/* + ADD build_dev_php.sh /build/php/ +RUN USER=root /build/php/build_dev_php.sh deps -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain 1.73.0 -y +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain 1.81.0 -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN USER=root /build/php/build_dev_php.sh deps diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy index 625e58c467..6a0928d299 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy @@ -1,6 +1,9 @@ package com.datadog.appsec.php.docker import com.datadog.appsec.php.mock_agent.MockDatadogAgent +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.Target import com.datadog.appsec.php.model.Span import com.datadog.appsec.php.model.Trace import com.github.dockerjava.api.command.CreateContainerCmd @@ -117,6 +120,14 @@ class AppSecContainer> extends GenericContain mockDatadogAgent.drainTelemetry(timeoutInMs) } + void setNextRCResponse(Target target, RemoteConfigResponse nextResponse) { + mockDatadogAgent.setNextRCResponse(target, nextResponse) + } + + RemoteConfigRequest waitForRCVersion(Target target, long version, long timeoutInMs) { + mockDatadogAgent.waitForRCVersion(target, version, timeoutInMs) + } + void close() { copyLogs() super.close() diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy index af6b52c189..e4bca9a83a 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy @@ -2,6 +2,7 @@ package com.datadog.appsec.php.mock_agent import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.Target import com.google.common.collect.Lists import groovy.transform.CompileStatic import groovy.util.logging.Slf4j @@ -13,29 +14,56 @@ import org.jetbrains.annotations.NotNull @CompileStatic @Singleton class ConfigV07Handler implements Handler { - RemoteConfigResponse nextResponse - final List capturedRequests = [] + private final Map nextResponse = [:] + final Map capturedRequests = [:] @Override void handle(@NotNull Context context) throws Exception { RemoteConfigRequest request = context.bodyStreamAsClass(RemoteConfigRequest) log.debug("Received request with version ${request.client.clientState.targetsVersion}: {}", request.toString()) + Target target = request.extractTarget() + RemoteConfigResponse resp + synchronized (this) { + resp = nextResponse[target] + nextResponse[target] = (RemoteConfigResponse) null + } synchronized (capturedRequests) { - capturedRequests.add(request) + capturedRequests[target] = request capturedRequests.notify() } - if (nextResponse != null) { - context.json(nextResponse) + if (resp != null) { + log.info("Sending RC response for {}, req targets version={}, resp targets version={}", + target, request.client.clientState.targetsVersion, resp.targetsSigned.version) + context.json(resp) } else { context.json([:]) } } - void setNextResponse(RemoteConfigResponse nextResponse) { - this.nextResponse = nextResponse + void setNextResponse(Target target, RemoteConfigResponse nextResponse) { + synchronized (this) { + this.nextResponse[target] = nextResponse + } + } + + RemoteConfigRequest waitForVersion(Target target, long version, long timeoutInMs) { + log.debug("waitForVersion start") + long deadline = System.currentTimeMillis() + timeoutInMs + synchronized (capturedRequests) { + while (System.currentTimeMillis() < deadline) { + RemoteConfigRequest request = capturedRequests[target] + if (request != null && request.client.clientState.targetsVersion == version) { + return request + } + capturedRequests.wait(deadline - System.currentTimeMillis()) + } + } + log.debug("waitForVersion timeout") + throw new AssertionError("No request with version $version gotten within " + + "$timeoutInMs ms for $target" as Object) } - List drain(long timeoutInMs) { + List> drain(long timeoutInMs) { synchronized (capturedRequests) { if (capturedRequests.isEmpty()) { capturedRequests.wait(timeoutInMs) diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy index 3895aaf2d5..1451e41588 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy @@ -1,6 +1,8 @@ package com.datadog.appsec.php.mock_agent - +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.Target import com.datadog.appsec.php.model.Trace import groovy.transform.CompileStatic import groovy.util.logging.Slf4j @@ -69,4 +71,12 @@ class MockDatadogAgent implements Startable { List drainTelemetry(int timeoutInMs) { TelemetryHandler.instance.drain(timeoutInMs) } + + void setNextRCResponse(Target target, RemoteConfigResponse nextResponse) { + ConfigV07Handler.instance.setNextResponse(target, nextResponse) + } + + RemoteConfigRequest waitForRCVersion(Target target, long version, long timeoutInMs) { + ConfigV07Handler.instance.waitForVersion(target, version, timeoutInMs) + } } diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java index f2db7ed5c1..2481f2517a 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java @@ -55,6 +55,13 @@ public ClientInfo getClient() { return this.client; } + public Target extractTarget() { + return Target.create( + client.tracerInfo.serviceName, + client.tracerInfo.serviceEnv, + client.tracerInfo.serviceVersion); + } + /** Stores client information for Remote Configuration */ public static class ClientInfo { @JsonProperty("state") @@ -75,8 +82,17 @@ public static class ClientInfo { @JsonProperty("is_agent") public Boolean isAgent = null; // MUST NOT be set; + @JsonProperty("last_seen") + public long lastSeen; + public byte[] capabilities; + @JsonProperty("is_updater") + public Boolean isUpdater = null; // MUST NOT be set; + + @JsonProperty("client_updater") + public Map clientUpdater; + public ClientInfo() {} public ClientInfo( ClientState clientState, @@ -118,13 +134,13 @@ public static class ClientState { public String error; @JsonProperty("backend_client_state") - public String backendClientState; + public byte[] backendClientState; public void setState( long targetsVersion, List configStates, String error, - String backendClientState) { + byte[] backendClientState) { this.targetsVersion = targetsVersion; this.configStates = configStates; this.error = error; @@ -136,8 +152,8 @@ public static class ConfigState { public static final int APPLY_STATE_ACKNOWLEDGED = 2; public static final int APPLY_STATE_ERROR = 3; - private String id; - private long version; + public String id; + public long version; public String product; @JsonProperty("apply_state") @@ -281,9 +297,9 @@ public boolean hashesMatch(Map hashesMap) { return true; } - public class TargetFileHash { - String algorithm; - String hash; + static public class TargetFileHash { + public String algorithm; + public String hash; TargetFileHash() {} TargetFileHash(String algorithm, String hash) { diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java index 0c77f63dc8..4ce3f5e6b2 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java @@ -1,12 +1,17 @@ package com.datadog.appsec.php.mock_agent.rem_cfg; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.IOException; +import java.io.StringWriter; import java.lang.reflect.UndeclaredThrowableException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -23,7 +28,8 @@ public class RemoteConfigResponse { public List clientConfigs; @JsonDeserialize(using = TargetsDeserializer.class) - private Targets targets; + @JsonSerialize(using = TargetsSerializer.class) + public Targets targets; @JsonProperty("target_files") public List targetFiles; @@ -103,7 +109,7 @@ public byte[] getFileContents(String configKey) { throw new MissingContentException("No content for " + configKey); } - private static BigInteger sha256(byte[] bytes) { + public static BigInteger sha256(byte[] bytes) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(bytes); @@ -136,6 +142,8 @@ public static class TargetsSigned { public String type; public TargetsCustom custom; + + @JsonSerialize(using=InstantSerializer.class) public Instant expires; @JsonProperty("spec_version") @@ -145,6 +153,9 @@ public static class TargetsSigned { public Map targets; public static class TargetsCustom { + @JsonProperty("agent_request_interval") + public long agentRequestInterval; + @JsonProperty("opaque_backend_state") public String opaqueBackendState; } @@ -183,4 +194,27 @@ public Targets deserialize(JsonParser jsonParser, DeserializationContext deseria return (Targets) defaultDeserializer.deserialize(defParser, deserializationContext); } } + + public static class TargetsSerializer extends JsonSerializer { + @Override + public void serialize(Targets value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + JsonSerializer defaultSerializer = serializers.findValueSerializer(Targets.class); + + StringWriter sw = new StringWriter(); + JsonGenerator defGen = gen.getCodec().getFactory().createGenerator(sw); + defaultSerializer.serialize(value, defGen, serializers); + defGen.flush(); + + byte[] base64 = Base64.getEncoder().encode(sw.toString().getBytes(StandardCharsets.UTF_8)); + + gen.writeString(new String(base64, StandardCharsets.ISO_8859_1)); + } + } + + public static class InstantSerializer extends JsonSerializer { + @Override + public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.toString()); + } + } } diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy new file mode 100644 index 0000000000..92ecb4ad94 --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy @@ -0,0 +1,14 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg + +import groovy.transform.Immutable + +@Immutable +class Target { + String service + String env + String appVersion + + static Target create(String service, String env, String appVersion) { + new Target(service, env, appVersion) + } +} diff --git a/appsec/tests/integration/src/test/bin/enable_extensions.sh b/appsec/tests/integration/src/test/bin/enable_extensions.sh index 7363a31907..3d91dadc5f 100755 --- a/appsec/tests/integration/src/test/bin/enable_extensions.sh +++ b/appsec/tests/integration/src/test/bin/enable_extensions.sh @@ -24,6 +24,7 @@ if [[ -f /appsec/ddappsec.so && -d /project ]]; then echo datadog.appsec.helper_path=/appsec/libddappsec-helper.so echo datadog.appsec.helper_log_file=/tmp/logs/helper.log echo datadog.appsec.helper_log_level=info +# echo datadog.appsec.helper_log_level=debug echo datadog.appsec.rules=/etc/recommended.json echo datadog.appsec.log_file=/tmp/logs/appsec.log echo datadog.appsec.log_level=debug diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy index f679392c45..25ec33cdd4 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy @@ -12,9 +12,7 @@ import org.testcontainers.junit.jupiter.Testcontainers import java.net.http.HttpResponse import static com.datadog.appsec.php.integration.TestParams.getPhpVersion -import static com.datadog.appsec.php.integration.TestParams.getTracerVersion import static com.datadog.appsec.php.integration.TestParams.getVariant -import static org.testcontainers.containers.Container.ExecResult @Testcontainers @Slf4j diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy new file mode 100644 index 0000000000..783d89a583 --- /dev/null +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy @@ -0,0 +1,388 @@ +package com.datadog.appsec.php.integration + +import com.datadog.appsec.php.docker.AppSecContainer +import com.datadog.appsec.php.docker.FailOnUnmatchedTraces +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.Target +import groovy.json.JsonOutput +import groovy.util.logging.Slf4j +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.DisabledIf +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers + +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.nio.charset.StandardCharsets +import java.time.Instant + +import static com.datadog.appsec.php.integration.TestParams.getPhpVersion +import static com.datadog.appsec.php.integration.TestParams.getVariant +import static java.net.http.HttpResponse.BodyHandlers.ofString +import static org.junit.jupiter.api.Assumptions.assumeTrue +import static org.testcontainers.containers.Container.ExecResult + +@Testcontainers +@Slf4j +@DisabledIf('isDisabled') +class RemoteConfigTests { + static boolean disabled = variant.contains('zts') || phpVersion != '8.3' + + private static final Target INITIAL_TARGET = new Target('some-name', 'none', '') + + @Container + @FailOnUnmatchedTraces + public static final AppSecContainer CONTAINER = + new AppSecContainer( + workVolume: this.name, + baseTag: 'nginx-fpm-php', + phpVersion: phpVersion, + phpVariant: variant, + www: 'base', + ) + + @BeforeAll + static void beforeAll() { + ExecResult res = CONTAINER.execInContainer( + 'bash', '-c', + '''sed -e '/appsec.enabled/d' -e '/appsec.rules=/d' /etc/php/php.ini > /etc/php/php-rc.ini; + kill -9 `pgrep php-fpm`; + export _DD_DEBUG_SIDECAR_RC_POLL_INTERVAL_MILLIS=1000 DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=1 DD_ENV=; + php-fpm -y /etc/php-fpm.conf -c /etc/php/php-rc.ini''') + assert res.exitCode == 0 + } + + @Test + void 'test remote activation'() { + def doReq = { int expectedStatus -> + HttpRequest req = CONTAINER.buildReq('/hello.php') + .GET() + .header('User-agent', 'dd-test-scanner-log-block') + .build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call(200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [ + asm: [enabled: true] + ] + ]) + + doReq.call(403) + + dropRemoteConfig(INITIAL_TARGET) + + doReq.call(200) + } + + @Test + void 'test asm_data'() { + def doReq = { String ip, int expectedStatus -> + HttpRequest req = CONTAINER.buildReq('/hello.php') + .GET() + .header('X-Real-Ip', ip) + .build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call('1.2.3.4', 200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], + 'datadog/2/ASM_DATA/mydata/config': [ + rules_data: [ + [ + id: 'blocked_ips', + type: 'ip_with_expiration', + data: [ + [ + expiration: 0, + value: '1.2.3.0/24' + ] + ] + ] + ] + + ] + ]) + + doReq.call('1.2.3.4', 403) + doReq.call('1.2.4.1', 200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + doReq.call('1.2.3.4', 200) + + dropRemoteConfig(INITIAL_TARGET) + } + + @Test + void 'test asm_dd'() { + def doReq = { int expectedStatus -> + HttpRequest req = CONTAINER.buildReq('/hello.php?a=matched+value').GET().build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call(200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], + 'datadog/2/ASM_DD/full_cfg/config': + [ + version: '2.1', + rules: [[ + id: 'partial_match_values', + name: 'Partially match values', + tags: [ + type: 'security_scanner', + category: 'attack_attempt' + ], + conditions: [[ + parameters: [ + inputs: [[ address: 'server.request.query' ]], + regex: '.*matched.+value.*' + ], + operator: 'match_regex' + ]], + transformers: ['values_only'], + on_match: ['block'] + ]] + ] + ]) + + doReq.call(403) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + doReq.call(200) + + dropRemoteConfig(INITIAL_TARGET) + } + + @Test + void 'test asm'() { + def doReq = { String path, int expectedStatus, Map headers = [:] -> + def br = CONTAINER.buildReq(path).GET() + headers.each { k, v -> br.header(k, v) } + HttpRequest req = br.build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call('/hello.php', 200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], + 'datadog/2/ASM/custom_user_cfg_2/config': [ + actions: [[ + id: 'block_custom', + type: 'block_request', + parameters: [ + status_code: '408' + ] + ]] + ], + 'datadog/2/ASM/custom_user_cfg_1/config': [ + custom_rules: [[ + id: 'partial_match_values', + name: 'Partially match values', + tags: [ + type: 'security_scanner', + category: 'attack_attempt' + ], + conditions: [[ + parameters: [ + inputs: [[ + address: 'server.request.query' + ]], + regex: '.*matched.+value.*' + ], + operator: 'match_regex' + ]], + transformers: ['values_only'], + on_match: ['block_custom'] + + ]], + exclusions: [[ + id: 'excl1', + rules_target: [[ + rule_id: 'ua0-600-56x' + ]], + conditions: [[ + operator: 'match_regex', + parameters: [ + inputs: [[ + address: 'server.request.query' + ]], + regex: 'excluded' + ] + ]] + ]], + rules_override: [[ + rules_target: [[ + rule_id: 'ua0-600-56x' + ]], + on_match: ['block_custom2'], + enabled: true + ]], + actions: [ + [ + id: 'block_custom2', + type: 'block_request', + parameters: [ + status_code: '410' + ] + ] + ] + ] + ]) + + // matches user rule 'partial_match_values' + doReq.call('/hello.php?a=matched+value1', 408) + + // matches exclusion rule 'excl1' + doReq.call('/hello.php?b=excluded', 200, ['User-agent': 'dd-test-scanner-log-block']) + + // action is overridden in rules_override to block_custom2 (code: 410) + doReq.call('/hello.php', 410, ['User-agent': 'dd-test-scanner-log-block']) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': null /* keep */, + 'datadog/2/ASM/custom_user_cfg_1/config': null /* keep */, + 'datadog/2/ASM/custom_user_cfg_2/config': [ + actions: [[ + id: 'block_custom', + type: 'block_request', + parameters: [ + status_code: '409' + ] + ]] + ], + ]) + + // status code changed to 409 + doReq.call('/hello.php?a=matched+value1', 409) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + doReq.call('/hello.php?a=matched+value1', 200) + doReq.call('/hello.php?b=excluded', 403, ['User-agent': 'dd-test-scanner-log-block']) + doReq.call('/hello.php', 403, ['User-agent': 'dd-test-scanner-log-block']) + + dropRemoteConfig(INITIAL_TARGET) + } + + @Test + void 'test env change'() { + Target newTarget = new Target('some-name', 'another-env', '') + + def doReq = { Integer expectedStatus, String path, Map headers = [:] -> + def br = CONTAINER.buildReq(path).GET() + headers.each { k, v -> br.header(k, v) } + HttpRequest req = br.build() + def gottenStatus = null + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + gottenStatus = resp.statusCode() + if (expectedStatus) { + assert gottenStatus == expectedStatus + } + } + gottenStatus + } + + doReq.call(200, '/hello.php') + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: false]]]) + + doReq.call(200, '/hello.php', ['User-agent': 'dd-test-scanner-log-block']) + + // changes env at the end of the request. The new rem cfg path is not transmitted + // to helper because appsec transmit rc path on req init + doReq.call(200, '/change_env.php?env=another-env') + + // new rem cfg path is transmitted to the helper on config_sync + doReq.call(200, '/change_env.php?env=another-env') + + applyRemoteConfig(newTarget, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + def status = doReq.call(null, '/hello.php', ['User-agent': 'dd-test-scanner-log-block']) + if (status == 200) { + assumeTrue(false, "Test fails because env of rc client is reset on ddtrace_sidecar_rinit(), " + + "which runs before appsec rinit") + } + assert status == 403 + + dropRemoteConfig(INITIAL_TARGET) + dropRemoteConfig(newTarget) + } + + private RemoteConfigRequest applyRemoteConfig(Target target, Map files) { + Map encodedFiles = files + .findAll { it.value != null } + .collectEntries { + [ + it.key, + JsonOutput.toJson(it.value).getBytes(StandardCharsets.UTF_8) + ] + } + long newVersion = Instant.now().epochSecond + def rcr = new RemoteConfigResponse() + rcr.clientConfigs = files.keySet() as List + rcr.targetFiles = encodedFiles.collect { + new RemoteConfigResponse.TargetFile( + path: it.key, + raw: new String( + Base64.encoder.encode(it.value), + StandardCharsets.ISO_8859_1) + ) + } + rcr.targets = new RemoteConfigResponse.Targets( + signatures: [], + targetsSigned: new RemoteConfigResponse.Targets.TargetsSigned( + type: 'root', + custom: new RemoteConfigResponse.Targets.TargetsSigned.TargetsCustom( + opaqueBackendState: 'ABCDEF' + ), + specVersion:'1.0.0', + expires: Instant.parse('2030-01-01T00:00:00Z'), + version: newVersion, + targets: encodedFiles.collectEntries { + [ + it.key, + new RemoteConfigResponse.Targets.ConfigTarget( + hashes: [sha256: RemoteConfigResponse.sha256(it.value).toString(16).padLeft(64, '0')], + length: it.value.size(), + custom: new RemoteConfigResponse.Targets.ConfigTarget.ConfigTargetCustom( + version: newVersion + ) + ) + ] + } + ), + ) + + CONTAINER.setNextRCResponse(target, rcr) + CONTAINER.waitForRCVersion(target, newVersion, 5_000) + } + + RemoteConfigRequest dropRemoteConfig(Target target) { + applyRemoteConfig(target, [:]) + } + +} diff --git a/appsec/tests/integration/src/test/www/base/public/change_env.php b/appsec/tests/integration/src/test/www/base/public/change_env.php new file mode 100644 index 0000000000..129a83b2fd --- /dev/null +++ b/appsec/tests/integration/src/test/www/base/public/change_env.php @@ -0,0 +1,7 @@ +env = $_GET['env']; + +var_dump($root_span); diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index 776c13459f..8bef6a8387 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -165,10 +165,12 @@ void ddog_reset_logger(void); uint32_t ddog_get_logs_count(ddog_CharSlice level); -void ddog_init_remote_config(bool live_debugging_enabled); +void ddog_init_remote_config(bool live_debugging_enabled, bool appsec_features, bool appsec_config); struct ddog_RemoteConfigState *ddog_init_remote_config_state(const struct ddog_Endpoint *endpoint); +const char *ddog_remote_config_get_path(const struct ddog_RemoteConfigState *remote_config); + void ddog_process_remote_configs(struct ddog_RemoteConfigState *remote_config); bool ddog_type_can_be_instrumented(const struct ddog_RemoteConfigState *remote_config, diff --git a/components-rs/remote_config.rs b/components-rs/remote_config.rs index 6bddeb4681..d164e200d9 100644 --- a/components-rs/remote_config.rs +++ b/components-rs/remote_config.rs @@ -1,31 +1,39 @@ -use std::borrow::Cow; +use crate::sidecar::MaybeShmLimiter; +use datadog_dynamic_configuration::{data::TracingSamplingRuleProvenance, Configs}; +use datadog_live_debugger::debugger_defs::{DebuggerData, DebuggerPayload}; use datadog_live_debugger::{FilterList, LiveDebuggingData, ServiceConfiguration}; -use datadog_live_debugger_ffi::data::{Probe}; +use datadog_live_debugger_ffi::data::Probe; use datadog_live_debugger_ffi::evaluator::{ddog_register_expr_evaluator, Evaluator}; -use datadog_dynamic_configuration::{Configs, data::TracingSamplingRuleProvenance}; -use datadog_remote_config::{RemoteConfigCapabilities, RemoteConfigData, RemoteConfigProduct, Target}; +use datadog_live_debugger_ffi::send_data::{ + ddog_debugger_diagnostics_create_unboxed, ddog_snapshot_redacted_type, +}; use datadog_remote_config::fetch::ConfigInvariants; +use datadog_remote_config::{ + RemoteConfigCapabilities, RemoteConfigData, RemoteConfigProduct, Target, +}; +use datadog_sidecar::service::blocking::SidecarTransport; +use datadog_sidecar::service::{InstanceId, QueueId}; use datadog_sidecar::shm_remote_config::{RemoteConfigManager, RemoteConfigUpdate}; +use datadog_sidecar_ffi::ddog_sidecar_send_debugger_data; use ddcommon::Endpoint; use ddcommon_ffi::slice::AsBytes; use ddcommon_ffi::{CharSlice, MaybeError}; use itertools::Itertools; -use std::collections::{HashMap, HashSet}; +use regex_automata::dfa::regex::Regex; +use serde::Serialize; +use std::borrow::Cow; use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; use std::ffi::c_char; use std::mem; use std::sync::Arc; -use serde::Serialize; -use crate::sidecar::MaybeShmLimiter; -use regex_automata::dfa::regex::Regex; use tracing::debug; -use datadog_live_debugger::debugger_defs::{DebuggerData, DebuggerPayload}; -use datadog_live_debugger_ffi::send_data::{ddog_debugger_diagnostics_create_unboxed, ddog_snapshot_redacted_type}; -use datadog_sidecar::service::blocking::SidecarTransport; -use datadog_sidecar::service::{InstanceId, QueueId}; -use datadog_sidecar_ffi::ddog_sidecar_send_debugger_data; -type DynamicConfigUpdate = for <'a> extern "C" fn(config: CharSlice, value: CharSlice, return_old: bool) -> *mut Vec; +type DynamicConfigUpdate = for<'a> extern "C" fn( + config: CharSlice, + value: CharSlice, + return_old: bool, +) -> *mut Vec; static mut LIVE_DEBUGGER_CALLBACKS: Option = None; static mut DYNAMIC_CONFIG_UPDATE: Option = None; @@ -36,7 +44,8 @@ pub static mut DDTRACE_REMOTE_CONFIG_PRODUCTS: VecRemoteConfigProduct = ddcommon type VecRemoteConfigCapabilities = ddcommon_ffi::Vec; #[no_mangle] -pub static mut DDTRACE_REMOTE_CONFIG_CAPABILITIES: VecRemoteConfigCapabilities = ddcommon_ffi::Vec::new(); +pub static mut DDTRACE_REMOTE_CONFIG_CAPABILITIES: VecRemoteConfigCapabilities = + ddcommon_ffi::Vec::new(); #[derive(Default)] struct DynamicConfig { @@ -74,8 +83,11 @@ pub struct LiveDebuggerState { } #[no_mangle] -pub unsafe extern "C" fn ddog_init_remote_config(live_debugging_enabled: bool) { - +pub unsafe extern "C" fn ddog_init_remote_config( + live_debugging_enabled: bool, + appsec_features: bool, + appsec_config: bool, +) { DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::ApmTracing); DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingCustomTags); DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingEnabled); @@ -87,6 +99,35 @@ pub unsafe extern "C" fn ddog_init_remote_config(live_debugging_enabled: bool) { if live_debugging_enabled { DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::LiveDebugger) } + + if appsec_features { + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmFeatures); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::AsmActivation); + } + + if appsec_config { + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmData); + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmDD); + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::Asm); + [ + RemoteConfigCapabilities::AsmIpBlocking, + RemoteConfigCapabilities::AsmDdRules, + RemoteConfigCapabilities::AsmExclusions, + RemoteConfigCapabilities::AsmRequestBlocking, + RemoteConfigCapabilities::AsmResponseBlocking, + RemoteConfigCapabilities::AsmUserBlocking, + RemoteConfigCapabilities::AsmCustomRules, + RemoteConfigCapabilities::AsmCustomBlockingResponse, + RemoteConfigCapabilities::AsmTrustedIps, + RemoteConfigCapabilities::AsmApiSecuritySampleRate, + RemoteConfigCapabilities::AsmProcessorOverrides, + RemoteConfigCapabilities::AsmCustomDataScanners, + RemoteConfigCapabilities::AsmExclusionData, + RemoteConfigCapabilities::AsmRaspSqli, + ] + .iter() + .for_each(|c| DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(*c)); + } } // Per-thread state @@ -122,31 +163,40 @@ struct SampleRule<'a> { fn map_config(config: &Configs) -> (&'static str, String) { match config { - Configs::TracingHeaderTags(tags) => { - ("datadog.trace.header_tags", tags.iter().map(|(k, _)| k).join(",")) - } - Configs::TracingSampleRate(rate) => { - ("datadog.trace.sample_rate", rate.to_string()) - } - Configs::LogInjectionEnabled(enabled) => { - ("datadog.logs_injection", (if *enabled { "1" } else { "0" }).to_string()) - } - Configs::TracingTags(tags) => { - ("datadog.tags", tags.join(",")) - } - Configs::TracingEnabled(enabled) => { - ("datadog.trace.enabled", (if *enabled { "1" } else { "0" }).to_string()) - } + Configs::TracingHeaderTags(tags) => ( + "datadog.trace.header_tags", + tags.iter().map(|(k, _)| k).join(","), + ), + Configs::TracingSampleRate(rate) => ("datadog.trace.sample_rate", rate.to_string()), + Configs::LogInjectionEnabled(enabled) => ( + "datadog.logs_injection", + (if *enabled { "1" } else { "0" }).to_string(), + ), + Configs::TracingTags(tags) => ("datadog.tags", tags.join(",")), + Configs::TracingEnabled(enabled) => ( + "datadog.trace.enabled", + (if *enabled { "1" } else { "0" }).to_string(), + ), Configs::TracingSamplingRules(rules) => { - let map: Vec<_> = rules.iter().map(|r| SampleRule { - name: r.name.as_deref(), - service: r.service.as_str(), - resource: r.resource.as_str(), - tags: r.tags.iter().map(|t| (t.key.as_str(), t.value_glob.as_str())).collect(), - provenance: r.provenance, - sample_rate: r.sample_rate, - }).collect(); - ("datadog.trace.sampling_rules", serde_json::to_string(&map).unwrap()) + let map: Vec<_> = rules + .iter() + .map(|r| SampleRule { + name: r.name.as_deref(), + service: r.service.as_str(), + resource: r.resource.as_str(), + tags: r + .tags + .iter() + .map(|t| (t.key.as_str(), t.value_glob.as_str())) + .collect(), + provenance: r.provenance, + sample_rate: r.sample_rate, + }) + .collect(); + ( + "datadog.trace.sampling_rules", + serde_json::to_string(&map).unwrap(), + ) } } } @@ -159,12 +209,17 @@ fn remove_old_configs(remote_config: &mut RemoteConfigState) { remote_config.dynamic_config.active_config_path = None; } -fn insert_new_configs(old_config_values: &mut HashMap>, old_configs: &mut Vec, new_configs: Vec) { +fn insert_new_configs( + old_config_values: &mut HashMap>, + old_configs: &mut Vec, + new_configs: Vec, +) { let mut found_configs = HashSet::new(); for config in new_configs.iter() { let (name, val) = map_config(config); let is_update = old_config_values.contains_key(name); - let original = unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()(name.into(), val.as_str().into(), !is_update); + let original = + unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()(name.into(), val.as_str().into(), !is_update); if !original.is_null() { old_config_values.insert(name.into(), *unsafe { Box::from_raw(original) }); } @@ -181,12 +236,25 @@ fn insert_new_configs(old_config_values: &mut HashMap>, old_ *old_configs = new_configs; } +#[no_mangle] +pub extern "C" fn ddog_remote_config_get_path(remote_config: &RemoteConfigState) -> *const c_char { + remote_config + .manager + .active_reader + .as_ref() + .map(|r| r.get_path().as_ptr()) + .unwrap_or(std::ptr::null()) +} + #[no_mangle] pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigState) { loop { match remote_config.manager.fetch_update() { RemoteConfigUpdate::None => break, - RemoteConfigUpdate::Add { value, limiter_index } => match value.data { + RemoteConfigUpdate::Add { + value, + limiter_index, + } => match value.data { RemoteConfigData::LiveDebugger(debugger) => { let val = Box::new((debugger, MaybeShmLimiter::open(limiter_index))); let rc_ref = unsafe { mem::transmute(remote_config as *mut _) }; // sigh, borrow checker @@ -196,39 +264,46 @@ pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigSt e.insert(val); e.into_mut() } - Entry::Vacant(e) => { - e.insert(val) - } + Entry::Vacant(e) => e.insert(val), }; apply_config(rc_ref, debugger, limiter); - }, + } RemoteConfigData::DynamicConfig(config_data) => { let configs: Vec = config_data.lib_config.into(); if !configs.is_empty() { - insert_new_configs(&mut remote_config.dynamic_config.old_config_values, &mut remote_config.dynamic_config.configs, configs); + insert_new_configs( + &mut remote_config.dynamic_config.old_config_values, + &mut remote_config.dynamic_config.configs, + configs, + ); remote_config.dynamic_config.active_config_path = Some(value.config_id); } - }, - RemoteConfigData::Ignored(_) => {} + } + RemoteConfigData::Ignored(_) => (), }, RemoteConfigUpdate::Remove(path) => match path.product { RemoteConfigProduct::LiveDebugger => { - if let Some(boxed) = remote_config.live_debugger.active.remove(&path.config_id) { + if let Some(boxed) = remote_config.live_debugger.active.remove(&path.config_id) + { remove_config(remote_config, &boxed.0); } - }, + } RemoteConfigProduct::ApmTracing => { if Some(path.config_id) == remote_config.dynamic_config.active_config_path { remove_old_configs(remote_config); } - }, - _ => {}, + } + _ => (), }, } } } -fn apply_config(remote_config: &mut RemoteConfigState, debugger: &LiveDebuggingData, limiter: &MaybeShmLimiter) { +fn apply_config( + remote_config: &mut RemoteConfigState, + debugger: &LiveDebuggingData, + limiter: &MaybeShmLimiter, +) { if let Some(callbacks) = unsafe { &LIVE_DEBUGGER_CALLBACKS } { match debugger { LiveDebuggingData::Probe(probe) => { @@ -240,7 +315,7 @@ fn apply_config(remote_config: &mut RemoteConfigState, debugger: &LiveDebuggingD .spans_map .insert(probe.id.clone(), hook_id); } - }, + } LiveDebuggingData::ServiceConfiguration(config) => { debug!("Applying live debugger service config {config:?}"); fn build_regex(list: &FilterList) -> Option { @@ -280,7 +355,7 @@ fn remove_config(remote_config: &mut RemoteConfigState, debugger: &LiveDebugging debug!("Removing live debugger probe {}", probe.id); (callbacks.remove_probe)(id); } - }, + } LiveDebuggingData::ServiceConfiguration(ServiceConfiguration { id, .. }) => { // There can only be one active service configuration, but I don't want to rely on the order of adding and removing service configurations if id == &remote_config.live_debugger.config_id { @@ -294,7 +369,10 @@ fn remove_config(remote_config: &mut RemoteConfigState, debugger: &LiveDebugging } #[no_mangle] -pub extern "C" fn ddog_type_can_be_instrumented(remote_config: &RemoteConfigState, typename: CharSlice) -> bool { +pub extern "C" fn ddog_type_can_be_instrumented( + remote_config: &RemoteConfigState, + typename: CharSlice, +) -> bool { if ddog_snapshot_redacted_type(typename) { return false; } @@ -316,7 +394,11 @@ pub extern "C" fn ddog_type_can_be_instrumented(remote_config: &RemoteConfigStat #[no_mangle] pub extern "C" fn ddog_global_log_probe_limiter_inc(remote_config: &RemoteConfigState) -> bool { - if let Some(boxed) = remote_config.live_debugger.active.get(&remote_config.live_debugger.config_id) { + if let Some(boxed) = remote_config + .live_debugger + .active + .get(&remote_config.live_debugger.config_id) + { if let (LiveDebuggingData::ServiceConfiguration(config), limiter) = &**boxed { limiter.inc(config.sampling_snapshots_per_second) } else { @@ -333,7 +415,12 @@ pub unsafe extern "C" fn ddog_CharSlice_to_owned(str: CharSlice) -> *mut Vec bool { +pub extern "C" fn ddog_remote_configs_service_env_change( + remote_config: &mut RemoteConfigState, + service: CharSlice, + env: CharSlice, + version: CharSlice, +) -> bool { let new_target = Target { service: service.to_utf8_lossy().to_string(), env: env.to_utf8_lossy().to_string(), @@ -348,13 +435,21 @@ pub extern "C" fn ddog_remote_configs_service_env_change(remote_config: &mut Rem remote_config.manager.track_target(&Arc::new(new_target)); ddog_process_remote_configs(remote_config); - + true } #[no_mangle] -pub unsafe extern "C" fn ddog_remote_config_alter_dynamic_config(remote_config: &mut RemoteConfigState, config: CharSlice, new_value: CharSlice) -> bool { - if let Some(entry) = remote_config.dynamic_config.old_config_values.get_mut(config.try_to_utf8().unwrap()) { +pub unsafe extern "C" fn ddog_remote_config_alter_dynamic_config( + remote_config: &mut RemoteConfigState, + config: CharSlice, + new_value: CharSlice, +) -> bool { + if let Some(entry) = remote_config + .dynamic_config + .old_config_values + .get_mut(config.try_to_utf8().unwrap()) + { *entry = new_value.as_slice().into(); return false; } @@ -362,7 +457,10 @@ pub unsafe extern "C" fn ddog_remote_config_alter_dynamic_config(remote_config: } #[no_mangle] -pub unsafe extern "C" fn ddog_setup_remote_config(update_config: DynamicConfigUpdate, setup: &LiveDebuggerSetup) { +pub unsafe extern "C" fn ddog_setup_remote_config( + update_config: DynamicConfigUpdate, + setup: &LiveDebuggerSetup, +) { ddog_register_expr_evaluator(setup.evaluator); DYNAMIC_CONFIG_UPDATE = Some(update_config); LIVE_DEBUGGER_CALLBACKS = Some(setup.callbacks.clone()); @@ -377,7 +475,10 @@ pub extern "C" fn ddog_rinit_remote_config(remote_config: &mut RemoteConfigState pub extern "C" fn ddog_rshutdown_remote_config(remote_config: &mut RemoteConfigState) { remote_config.live_debugger.spans_map.clear(); remote_config.dynamic_config.old_config_values.clear(); - remote_config.manager.unload_configs(&[RemoteConfigProduct::ApmTracing, RemoteConfigProduct::LiveDebugger]); + remote_config.manager.unload_configs(&[ + RemoteConfigProduct::ApmTracing, + RemoteConfigProduct::LiveDebugger, + ]); } #[no_mangle] @@ -386,21 +487,51 @@ pub extern "C" fn ddog_shutdown_remote_config(_: Box) {} #[no_mangle] pub extern "C" fn ddog_log_debugger_data(payloads: &Vec) { if !payloads.is_empty() { - debug!("Submitting debugger data: {}", serde_json::to_string(payloads).unwrap()); + debug!( + "Submitting debugger data: {}", + serde_json::to_string(payloads).unwrap() + ); } } #[no_mangle] pub extern "C" fn ddog_log_debugger_datum(payload: &DebuggerPayload) { - debug!("Submitting debugger data: {}", serde_json::to_string(payload).unwrap()); + debug!( + "Submitting debugger data: {}", + serde_json::to_string(payload).unwrap() + ); } #[no_mangle] -pub unsafe extern "C" fn ddog_send_debugger_diagnostics<'a>(remote_config_state: &RemoteConfigState, transport: &mut Box, instance_id: &InstanceId, queue_id: QueueId, probe: &'a Probe, timestamp: u64) -> MaybeError { - let service = Cow::Borrowed(remote_config_state.manager.get_target().map_or("", |t| t.service.as_str())); - let mut payload = ddog_debugger_diagnostics_create_unboxed(probe, service, Cow::Borrowed(&instance_id.runtime_id), timestamp); - let DebuggerData::Diagnostics(ref mut diagnostics) = payload.debugger else { unreachable!(); }; - diagnostics.parent_id = Some(Cow::Borrowed(remote_config_state.manager.current_runtime_id.as_str())); - debug!("Submitting debugger diagnostics data: {:?}", serde_json::to_string(&payload).unwrap()); +pub unsafe extern "C" fn ddog_send_debugger_diagnostics<'a>( + remote_config_state: &RemoteConfigState, + transport: &mut Box, + instance_id: &InstanceId, + queue_id: QueueId, + probe: &'a Probe, + timestamp: u64, +) -> MaybeError { + let service = Cow::Borrowed( + remote_config_state + .manager + .get_target() + .map_or("", |t| t.service.as_str()), + ); + let mut payload = ddog_debugger_diagnostics_create_unboxed( + probe, + service, + Cow::Borrowed(&instance_id.runtime_id), + timestamp, + ); + let DebuggerData::Diagnostics(ref mut diagnostics) = payload.debugger else { + unreachable!(); + }; + diagnostics.parent_id = Some(Cow::Borrowed( + remote_config_state.manager.current_runtime_id.as_str(), + )); + debug!( + "Submitting debugger diagnostics data: {:?}", + serde_json::to_string(&payload).unwrap() + ); ddog_sidecar_send_debugger_data(transport, instance_id, queue_id, vec![payload]) } diff --git a/ddtrace.sym b/ddtrace.sym index c3e558bafd..d20803d5d3 100644 --- a/ddtrace.sym +++ b/ddtrace.sym @@ -6,5 +6,12 @@ ddtrace_runtime_id ddtrace_user_req_add_listeners ddtrace_ip_extraction_find ddtrace_set_all_thread_vm_interrupt +ddtrace_remote_config_get_path +ddog_remote_config_reader_for_path +ddog_remote_config_read +ddog_remote_config_reader_drop get_module ddog_daemon_entry_point +ddog_set_rc_notify_fn +ddog_remote_config_path +ddog_remote_config_path_free diff --git a/dockerfiles/verify_packages/verify.sh b/dockerfiles/verify_packages/verify.sh index 480391119f..1e4195a0a8 100755 --- a/dockerfiles/verify_packages/verify.sh +++ b/dockerfiles/verify_packages/verify.sh @@ -2,6 +2,8 @@ set -e +export DD_REMOTE_CONFIG_ENABLED=false + # Installing generic dependencies. OS_ID='centos'|'debian'|'alpine' OS_ID=$(. /etc/os-release; echo $ID) sh $(pwd)/dockerfiles/verify_packages/${OS_ID}/install.sh diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 4e8fcb3c35..9578861dbb 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -395,6 +395,8 @@ bool ddtrace_alter_dd_version(zval *old_value, zval *new_value, zend_string *new return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_version), old_value, new_value, new_str); } +static zend_module_entry *dd_appsec_module() { return zend_hash_str_find_ptr(&module_registry, "ddappsec", sizeof("ddappsec") - 1); } + static void dd_activate_once(void) { ddtrace_config_first_rinit(); ddtrace_generate_runtime_id(); @@ -416,8 +418,8 @@ static void dd_activate_once(void) { bgs_service = ddtrace_default_service_name(); } } - zend_module_entry *appsec_module = zend_hash_str_find_ptr(&module_registry, "ddappsec", sizeof("ddappsec") - 1); - if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER() || appsec_module) + if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER() || + (dd_appsec_module() != NULL && !get_global_DD_APPSEC_TESTING())) #endif { bool request_startup = PG(during_request_startup); diff --git a/ext/remote_config.c b/ext/remote_config.c index 867281f432..a4291804bc 100644 --- a/ext/remote_config.c +++ b/ext/remote_config.c @@ -57,6 +57,13 @@ DDTRACE_PUBLIC void ddtrace_set_all_thread_vm_interrupt(void) { #endif } +DDTRACE_PUBLIC const char *ddtrace_remote_config_get_path() { + if (DDTRACE_G(remote_config_state)) { + return ddog_remote_config_get_path(DDTRACE_G(remote_config_state)); + } + return NULL; +} + #ifndef _WIN32 static void dd_sigvtalarm_handler(int signal, siginfo_t *siginfo, void *ctx) { UNUSED(signal, siginfo, ctx); diff --git a/ext/remote_config.h b/ext/remote_config.h index cac1c89d2c..9e27fc403f 100644 --- a/ext/remote_config.h +++ b/ext/remote_config.h @@ -9,5 +9,6 @@ void ddtrace_rinit_remote_config(void); void ddtrace_rshutdown_remote_config(void); DDTRACE_PUBLIC void ddtrace_set_all_thread_vm_interrupt(void); +DDTRACE_PUBLIC const char *ddtrace_remote_config_get_path(void); #endif diff --git a/ext/sidecar.c b/ext/sidecar.c index eed8124481..f3d5d6037e 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -98,7 +98,10 @@ ddog_SidecarTransport *dd_sidecar_connection_factory(void) { return sidecar_transport; } -static void maybe_enable_appsec() { +static void maybe_enable_appsec(bool *appsec_features, bool *appsec_config) { + *appsec_features = false; + *appsec_config = false; + // this must be done in ddtrace rather than ddappsec because // the sidecar is launched by ddtrace before ddappsec has a chance // to run its first rinit @@ -117,6 +120,14 @@ static void maybe_enable_appsec() { } void (*dd_appsec_maybe_enable_helper)(typeof(&ddog_sidecar_enable_appsec) enable_appsec) = handle; dd_appsec_maybe_enable_helper(ddog_sidecar_enable_appsec); + + typedef void (*dd_appsec_rc_conf_t)(bool *, bool *); + dd_appsec_rc_conf_t dd_appsec_rc_conf = dlsym(RTLD_DEFAULT, "dd_appsec_rc_conf"); + if (dd_appsec_rc_conf) { + dd_appsec_rc_conf(appsec_features, appsec_config); + } else { + LOG(WARN, "Could not resolve dd_appsec_rc_conf"); + } #endif } @@ -124,9 +135,11 @@ void ddtrace_sidecar_setup(void) { ddtrace_set_non_resettable_sidecar_globals(); ddtrace_set_resettable_sidecar_globals(); - maybe_enable_appsec(); + bool appsec_features; + bool appsec_config; + maybe_enable_appsec(&appsec_features, &appsec_config); - ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()); + ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_features, appsec_config); ddtrace_sidecar = dd_sidecar_connection_factory(); if (!ddtrace_sidecar && ddtrace_endpoint) { // Something went wrong diff --git a/tests/ext/extension_no_static_tls.phpt b/tests/ext/extension_no_static_tls.phpt index d331e5acae..6ae89ca2fd 100644 --- a/tests/ext/extension_no_static_tls.phpt +++ b/tests/ext/extension_no_static_tls.phpt @@ -26,7 +26,7 @@ if (!file_exists('/proc/self/maps')) { die('skip: no /proc/self/maps'); } // 5. Determine in which compilation unit(s) this function is defined $maps = file_get_contents('/proc/self/maps'); -if (preg_match('@(?<=\\s)\\S*/ddtrace[^/]*\\.so$@m', $maps, $m) != 1) { +if (preg_match('@(?<=\\s)\\S*/ddtrace\\.so$@m', $maps, $m) != 1) { die('cannot find loaded ddtrace.so'); } diff --git a/tests/ext/live-debugger/exception-replay_001.phpt b/tests/ext/live-debugger/exception-replay_001.phpt index e8cdd472b7..604d8e376f 100644 --- a/tests/ext/live-debugger/exception-replay_001.phpt +++ b/tests/ext/live-debugger/exception-replay_001.phpt @@ -8,7 +8,7 @@ DD_AGENT_HOST=request-replayer DD_TRACE_AGENT_PORT=80 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_EXCEPTION_REPLAY_ENABLED=1 -DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1 +DD_EXCEPTION_REPLAY_RATE_LIMIT_SECONDS=1 --INI-- datadog.trace.agent_test_session_token=live-debugger/exception-replay_001 --FILE-- diff --git a/tests/ext/live-debugger/exception-replay_002.phpt b/tests/ext/live-debugger/exception-replay_002.phpt index 1880a57a5b..ceec8ac97f 100644 --- a/tests/ext/live-debugger/exception-replay_002.phpt +++ b/tests/ext/live-debugger/exception-replay_002.phpt @@ -7,7 +7,7 @@ DD_AGENT_HOST=request-replayer DD_TRACE_AGENT_PORT=80 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_EXCEPTION_REPLAY_ENABLED=1 -DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1 +DD_EXCEPTION_REPLAY_RATE_LIMIT_SECONDS=1 --INI-- datadog.trace.agent_test_session_token=live-debugger/exception-replay_002 --FILE-- From 708dbe4eaf224318bde2a695952722694d63b64b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 4 Oct 2024 10:34:34 +0100 Subject: [PATCH 2/8] wip --- appsec/src/helper/remote_config/client.cpp | 38 ++++++++++++++ appsec/src/helper/remote_config/client.hpp | 5 +- appsec/src/helper/remote_config/config.cpp | 2 +- .../php/mock_agent/rem_cfg/Capability.groovy | 51 +++++++++++++++++++ .../src/test/bin/enable_extensions.sh | 4 +- .../php/integration/RemoteConfigTests.groovy | 22 ++++++-- components-rs/remote_config.rs | 5 -- 7 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy diff --git a/appsec/src/helper/remote_config/client.cpp b/appsec/src/helper/remote_config/client.cpp index 32d96c5c09..bdb47d2333 100644 --- a/appsec/src/helper/remote_config/client.cpp +++ b/appsec/src/helper/remote_config/client.cpp @@ -27,6 +27,27 @@ ddog_RemoteConfigReader *(*ddog_remote_config_reader_for_path)( bool (*ddog_remote_config_read)( ddog_RemoteConfigReader *reader, ddog_CharSlice *data); void (*ddog_remote_config_reader_drop)(struct ddog_RemoteConfigReader *); + +// if before is the same as after, disconsidering elements not in products +bool has_updates( + const std::unordered_set &products, + const std::set &before, + const std::set &after) +{ + auto set_is_subset_of = [&products](auto &set1, auto &set2) { + for (auto &&elem_set_1 : set1) { // NOLINT(readability-use-anyofallof) + if (!products.contains(elem_set_1.get_product())) { + continue; + } + if (!set2.contains(elem_set_1)) { + return false; + } + } + return true; + }; + + return !set_is_subset_of(before, after) || !set_is_subset_of(after, before); +} } // namespace namespace dds::remote_config { @@ -73,6 +94,7 @@ client::client(remote_config::settings settings, std::vector &vec_listeners = listeners_per_product_[p]; vec_listeners.push_back(listener.get()); + all_products_.insert(p); } } } @@ -86,6 +108,8 @@ std::unique_ptr client::from_settings( bool client::poll() { + const std::lock_guard lock{mutex_}; + SPDLOG_DEBUG("Polling remote config"); ddog_CharSlice slice{}; @@ -116,6 +140,20 @@ bool client::poll() configs = configs.substr(pos_lf + 1); } + if (!has_updates(all_products_, new_configs, last_configs_)) { + SPDLOG_DEBUG("Configuration is identical for the subscribed products. " + "Skipping update"); + SPDLOG_DEBUG("BEFORE:"); + for (auto &&c : last_configs_) { + SPDLOG_DEBUG("{}:{}", c.rc_path, c.shm_path); + } + SPDLOG_DEBUG("AFTER:"); + for (auto &&c : last_configs_) { + SPDLOG_DEBUG("{}:{}", c.rc_path, c.shm_path); + } + return false; + } + return process_response(std::move(new_configs)); } diff --git a/appsec/src/helper/remote_config/client.hpp b/appsec/src/helper/remote_config/client.hpp index c5b22af2c8..7d03c0d94d 100644 --- a/appsec/src/helper/remote_config/client.hpp +++ b/appsec/src/helper/remote_config/client.hpp @@ -6,6 +6,7 @@ #pragma once #include +#include #include #include #include @@ -62,9 +63,11 @@ class client { std::vector> listeners_; std::unordered_map> - listeners_per_product_; // non-owning index of listeners_ + listeners_per_product_; // non-owning index of listeners_ + std::unordered_set all_products_; // keys of listeners_per_product_ std::set last_configs_; + std::mutex mutex_; }; } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/config.cpp b/appsec/src/helper/remote_config/config.cpp index 7914ba5e6f..dda0a7e28f 100644 --- a/appsec/src/helper/remote_config/config.cpp +++ b/appsec/src/helper/remote_config/config.cpp @@ -32,7 +32,7 @@ namespace dds::remote_config { } } else if (sv.starts_with("employee/"sv)) { sv.remove_prefix("employee/"sv.length()); - auto product_end = shm_path.find('/'); + auto product_end = sv.find('/'); if (product_end != std::string::npos) { return known_products::for_name(sv.substr(0, product_end)); } diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy new file mode 100644 index 0000000000..c52e9c01fa --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy @@ -0,0 +1,51 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg + +enum Capability { + ASM_ACTIVATION(1), + ASM_IP_BLOCKING(2), + ASM_DD_RULES(3), + ASM_EXCLUSIONS(4), + ASM_REQUEST_BLOCKING(5), + ASM_RESPONSE_BLOCKING(6), + ASM_USER_BLOCKING(7), + ASM_CUSTOM_RULES(8), + ASM_CUSTOM_BLOCKING_RESPONSE(9), + ASM_TRUSTED_IPS(10), + ASM_API_SECURITY_SAMPLE_RATE(11), + APM_TRACING_SAMPLE_RATE(12), + APM_TRACING_LOGS_INJECTION(13), + APM_TRACING_HTTP_HEADER_TAGS(14), + APM_TRACING_CUSTOM_TAGS(15), + ASM_PROCESSOR_OVERRIDES(16), + ASM_CUSTOM_DATA_SCANNERS(17), + ASM_EXCLUSION_DATA(18), + APM_TRACING_ENABLED(19), + APM_TRACING_DATA_STREAMS_ENABLED(20), + ASM_RASP_SQLI(21), + ASM_RASP_LFI(22), + ASM_RASP_SSRF(23), + ASM_RASP_SHI(24), + ASM_RASP_XXE(25), + ASM_RASP_RCE(26), + ASM_RASP_NOSQLI(27), + ASM_RASP_XSS(28), + APM_TRACING_SAMPLE_RULES(29), + CSM_ACTIVATION(30) + + final int ordinal + + Capability(int ordinal) { + this.ordinal = ordinal + } + + static EnumSet forByteArray(byte[] arr) { + def capabilities = EnumSet.noneOf(Capability) + def bi = new BigInteger(arr) + for (Capability c: values()) { + if (bi.testBit(c.ordinal)) { + capabilities << c + } + } + capabilities + } +} diff --git a/appsec/tests/integration/src/test/bin/enable_extensions.sh b/appsec/tests/integration/src/test/bin/enable_extensions.sh index 3d91dadc5f..ec9674b2bc 100755 --- a/appsec/tests/integration/src/test/bin/enable_extensions.sh +++ b/appsec/tests/integration/src/test/bin/enable_extensions.sh @@ -23,8 +23,8 @@ if [[ -f /appsec/ddappsec.so && -d /project ]]; then echo datadog.appsec.enabled=true echo datadog.appsec.helper_path=/appsec/libddappsec-helper.so echo datadog.appsec.helper_log_file=/tmp/logs/helper.log - echo datadog.appsec.helper_log_level=info -# echo datadog.appsec.helper_log_level=debug +# echo datadog.appsec.helper_log_level=info + echo datadog.appsec.helper_log_level=debug echo datadog.appsec.rules=/etc/recommended.json echo datadog.appsec.log_file=/tmp/logs/appsec.log echo datadog.appsec.log_level=debug diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy index 783d89a583..b06027dca1 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy @@ -2,6 +2,7 @@ package com.datadog.appsec.php.integration import com.datadog.appsec.php.docker.AppSecContainer import com.datadog.appsec.php.docker.FailOnUnmatchedTraces +import com.datadog.appsec.php.mock_agent.rem_cfg.Capability import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse import com.datadog.appsec.php.mock_agent.rem_cfg.Target @@ -55,7 +56,7 @@ class RemoteConfigTests { } @Test - void 'test remote activation'() { + void 'test remote activation and capabilities'() { def doReq = { int expectedStatus -> HttpRequest req = CONTAINER.buildReq('/hello.php') .GET() @@ -68,12 +69,27 @@ class RemoteConfigTests { doReq.call(200) - applyRemoteConfig(INITIAL_TARGET, [ + RemoteConfigRequest rcr = applyRemoteConfig(INITIAL_TARGET, [ 'datadog/2/ASM_FEATURES/asm_features_activation/config': [ asm: [enabled: true] ] ]) + def capSet = Capability.forByteArray(rcr.client.capabilities) + + [ + Capability.ASM_ACTIVATION, + Capability.ASM_IP_BLOCKING, + Capability.ASM_DD_RULES, + Capability.ASM_EXCLUSIONS, + Capability.ASM_REQUEST_BLOCKING, + Capability.ASM_RESPONSE_BLOCKING, + Capability.ASM_USER_BLOCKING, + Capability.ASM_CUSTOM_RULES, + Capability.ASM_CUSTOM_BLOCKING_RESPONSE, + Capability.ASM_TRUSTED_IPS, + ].each { assert it in capSet } + doReq.call(403) dropRemoteConfig(INITIAL_TARGET) @@ -138,7 +154,7 @@ class RemoteConfigTests { applyRemoteConfig(INITIAL_TARGET, [ 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], - 'datadog/2/ASM_DD/full_cfg/config': + 'employee/ASM_DD/full_cfg/config': [ version: '2.1', rules: [[ diff --git a/components-rs/remote_config.rs b/components-rs/remote_config.rs index d164e200d9..7952c37353 100644 --- a/components-rs/remote_config.rs +++ b/components-rs/remote_config.rs @@ -119,11 +119,6 @@ pub unsafe extern "C" fn ddog_init_remote_config( RemoteConfigCapabilities::AsmCustomRules, RemoteConfigCapabilities::AsmCustomBlockingResponse, RemoteConfigCapabilities::AsmTrustedIps, - RemoteConfigCapabilities::AsmApiSecuritySampleRate, - RemoteConfigCapabilities::AsmProcessorOverrides, - RemoteConfigCapabilities::AsmCustomDataScanners, - RemoteConfigCapabilities::AsmExclusionData, - RemoteConfigCapabilities::AsmRaspSqli, ] .iter() .for_each(|c| DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(*c)); From b424aabafa8393971905672952e7397c8daaefa3 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 4 Oct 2024 10:57:52 +0100 Subject: [PATCH 3/8] Fixes after review --- appsec/src/extension/ddappsec.c | 7 ++++--- appsec/src/helper/main.cpp | 2 +- appsec/src/helper/remote_config/client.cpp | 20 ++++++-------------- appsec/src/helper/remote_config/config.hpp | 8 +++++++- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/appsec/src/extension/ddappsec.c b/appsec/src/extension/ddappsec.c index f7a9567052..eb840d940a 100644 --- a/appsec/src/extension/ddappsec.c +++ b/appsec/src/extension/ddappsec.c @@ -402,13 +402,14 @@ __attribute__((visibility("default"))) void dd_appsec_rc_conf( bool prev_active = DDAPPSEC_G(active); bool prev_to_be_configured = DDAPPSEC_G(to_be_configured); _check_enabled(); - DDAPPSEC_G(enabled) = prev_enabled; - DDAPPSEC_G(active) = prev_active; - DDAPPSEC_G(to_be_configured) = prev_to_be_configured; *appsec_features = DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG; // only enable ASM / ASM_DD / ASM_DATA if no rules file is specified *appsec_conf = get_global_DD_APPSEC_RULES()->len == 0; + + DDAPPSEC_G(enabled) = prev_enabled; + DDAPPSEC_G(active) = prev_active; + DDAPPSEC_G(to_be_configured) = prev_to_be_configured; } static PHP_FUNCTION(datadog_appsec_is_enabled) diff --git a/appsec/src/helper/main.cpp b/appsec/src/helper/main.cpp index c76d880910..68a8f69166 100644 --- a/appsec/src/helper/main.cpp +++ b/appsec/src/helper/main.cpp @@ -1,5 +1,5 @@ -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. // 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. diff --git a/appsec/src/helper/remote_config/client.cpp b/appsec/src/helper/remote_config/client.cpp index bdb47d2333..5b851b4bb3 100644 --- a/appsec/src/helper/remote_config/client.cpp +++ b/appsec/src/helper/remote_config/client.cpp @@ -28,8 +28,7 @@ bool (*ddog_remote_config_read)( ddog_RemoteConfigReader *reader, ddog_CharSlice *data); void (*ddog_remote_config_reader_drop)(struct ddog_RemoteConfigReader *); -// if before is the same as after, disconsidering elements not in products -bool has_updates( +bool sets_are_indentical_for_subbed_products( const std::unordered_set &products, const std::set &before, const std::set &after) @@ -46,7 +45,7 @@ bool has_updates( return true; }; - return !set_is_subset_of(before, after) || !set_is_subset_of(after, before); + return set_is_subset_of(before, after) && set_is_subset_of(after, before); } } // namespace @@ -87,7 +86,7 @@ client::client(remote_config::settings settings, ddog_remote_config_reader_drop}, settings_{std::move(settings)}, listeners_{std::move(listeners)} { - assert(settings.enabled == true); // NOLINT + assert(settings_.enabled == true); // NOLINT for (auto const &listener : listeners_) { for (const product p : listener->get_supported_products()) { @@ -140,17 +139,10 @@ bool client::poll() configs = configs.substr(pos_lf + 1); } - if (!has_updates(all_products_, new_configs, last_configs_)) { - SPDLOG_DEBUG("Configuration is identical for the subscribed products. " + if (sets_are_indentical_for_subbed_products( + all_products_, new_configs, last_configs_)) { + SPDLOG_DEBUG("Configuration is identical for the subscribed products. " "Skipping update"); - SPDLOG_DEBUG("BEFORE:"); - for (auto &&c : last_configs_) { - SPDLOG_DEBUG("{}:{}", c.rc_path, c.shm_path); - } - SPDLOG_DEBUG("AFTER:"); - for (auto &&c : last_configs_) { - SPDLOG_DEBUG("{}:{}", c.rc_path, c.shm_path); - } return false; } diff --git a/appsec/src/helper/remote_config/config.hpp b/appsec/src/helper/remote_config/config.hpp index cf3b615f51..889a29ff2b 100644 --- a/appsec/src/helper/remote_config/config.hpp +++ b/appsec/src/helper/remote_config/config.hpp @@ -48,7 +48,13 @@ template <> struct less { bool operator()(const dds::remote_config::config &lhs, const dds::remote_config::config &rhs) const { - return lhs.rc_path < rhs.rc_path; + if (lhs.rc_path < rhs.rc_path) { + return true; + }; + if (lhs.rc_path > rhs.rc_path) { + return false; + } + return lhs.shm_path < rhs.shm_path; } }; } // namespace std From bee3eef8e4f63a4b68dfbec0f920e28aa59d9d3f Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 4 Oct 2024 16:33:44 +0100 Subject: [PATCH 4/8] More review changes --- appsec/src/helper/json_helper.cpp | 4 +- appsec/src/helper/json_helper.hpp | 2 +- appsec/src/helper/remote_config/config.cpp | 17 +-- appsec/src/helper/remote_config/config.hpp | 44 +++++- .../listeners/asm_features_listener.cpp | 9 +- .../tests/extension/push_params_block_02.phpt | 47 +++++++ .../listeners/asm_features_listener_test.cpp | 15 +- .../asm_aggregator_test.cpp | 130 +++++++++--------- .../asm_data_aggregator_test.cpp | 50 +++---- .../asm_dd_aggregator_test.cpp | 10 +- .../listeners/engine_listener_test.cpp | 82 ++++++----- appsec/tests/helper/remote_config/mocks.cpp | 5 +- appsec/tests/helper/remote_config/mocks.hpp | 3 +- .../src/test/bin/enable_extensions.sh | 4 +- 14 files changed, 241 insertions(+), 181 deletions(-) create mode 100644 appsec/tests/extension/push_params_block_02.phpt diff --git a/appsec/src/helper/json_helper.cpp b/appsec/src/helper/json_helper.cpp index 4712ddfc15..9a785ddc7e 100644 --- a/appsec/src/helper/json_helper.cpp +++ b/appsec/src/helper/json_helper.cpp @@ -201,9 +201,9 @@ json_helper::get_field_of_type( } bool json_helper::parse_json( - const std::string &content, rapidjson::Document &output) + std::string_view content, rapidjson::Document &output) { - if (output.Parse(content).HasParseError()) { + if (output.Parse(content.data(), content.size()).HasParseError()) { SPDLOG_DEBUG("Invalid json: " + std::string(rapidjson::GetParseError_En( output.GetParseError()))); return false; diff --git a/appsec/src/helper/json_helper.hpp b/appsec/src/helper/json_helper.hpp index 52b4db8901..42e07c046e 100644 --- a/appsec/src/helper/json_helper.hpp +++ b/appsec/src/helper/json_helper.hpp @@ -63,7 +63,7 @@ std::optional get_field_of_type( std::optional get_field_of_type( rapidjson::Value::ConstValueIterator parent_field, std::string_view key, rapidjson::Type type); -bool parse_json(const std::string &content, rapidjson::Document &output); +bool parse_json(std::string_view content, rapidjson::Document &output); // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) void merge_arrays(rapidjson::Value &destination, rapidjson::Value &source, rapidjson::Value::AllocatorType &allocator); diff --git a/appsec/src/helper/remote_config/config.cpp b/appsec/src/helper/remote_config/config.cpp index dda0a7e28f..d324d6ff74 100644 --- a/appsec/src/helper/remote_config/config.cpp +++ b/appsec/src/helper/remote_config/config.cpp @@ -69,7 +69,7 @@ config config::from_line(std::string_view line) return {std::string{shm_path}, std::move(rc_path)}; } -std::string config::read() const +mapped_memory config::read() const { // open shared memory segment at rc_path: const int fd = ::shm_open(shm_path.c_str(), O_RDONLY, 0); @@ -100,19 +100,6 @@ std::string config::read() const "Failed to map shared memory: " + std::string{strerror_ts(errno)}); } - auto unmap = defer{[shm_ptr, &shm_stat]() { - if (::munmap(shm_ptr, shm_stat.st_size) == -1) { - // NOLINTNEXTLINE(bugprone-lambda-function-name) - SPDLOG_WARN( - "Failed to unmap shared memory: {}", strerror_ts(errno)); - } - }}; - - std::string result; - result.resize(shm_stat.st_size); - - std::copy_n(static_cast(shm_ptr), shm_stat.st_size, result.begin()); - - return result; + return mapped_memory{shm_ptr, static_cast(shm_stat.st_size)}; } } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/config.hpp b/appsec/src/helper/remote_config/config.hpp index 889a29ff2b..05e5e226c7 100644 --- a/appsec/src/helper/remote_config/config.hpp +++ b/appsec/src/helper/remote_config/config.hpp @@ -11,8 +11,50 @@ #include #include +extern "C" { +#include +} + namespace dds::remote_config { +class mapped_memory { +public: + mapped_memory(void *ptr, std::size_t size) : ptr_{ptr}, size_{size} {} + mapped_memory(const mapped_memory &) = delete; + mapped_memory(mapped_memory &&mm) noexcept : ptr_{mm.ptr_}, size_{mm.size_} + { + mm.ptr_ = nullptr; + mm.size_ = 0; + } + mapped_memory &operator=(const mapped_memory &) = delete; + mapped_memory &operator=(mapped_memory &&mm) noexcept + { + ptr_ = mm.ptr_; + size_ = mm.size_; + mm.ptr_ = nullptr; + mm.size_ = 0; + return *this; + } + ~mapped_memory() noexcept + { + if (ptr_ != nullptr) { + if (::munmap(ptr_, size_) == -1) { + SPDLOG_WARN( + "Failed to unmap shared memory: {}", strerror_ts(errno)); + }; + } + } + + operator std::string_view() const // NOLINT + { + return std::string_view{static_cast(ptr_), size_}; + } + +private: + void *ptr_; + std::size_t size_; +}; + struct config { // from a line provided by the RC config reader static config from_line(std::string_view line); @@ -20,7 +62,7 @@ struct config { std::string shm_path; std::string rc_path; - [[nodiscard]] std::string read() const; + [[nodiscard]] mapped_memory read() const; [[nodiscard]] product get_product() const; diff --git a/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp b/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp index 5b6cd791de..71c293a04d 100644 --- a/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp +++ b/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp @@ -12,10 +12,13 @@ void dds::remote_config::asm_features_listener::on_update(const config &config) { - const std::string contents{config.read()}; rapidjson::Document serialized_doc; - if (!json_helper::parse_json(contents, serialized_doc)) { - throw error_applying_config("Invalid config contents"); + + { + const mapped_memory contents{config.read()}; + if (!json_helper::parse_json(contents, serialized_doc)) { + throw error_applying_config("Invalid config contents"); + } } auto asm_itr = json_helper::get_field_of_type( diff --git a/appsec/tests/extension/push_params_block_02.phpt b/appsec/tests/extension/push_params_block_02.phpt new file mode 100644 index 0000000000..04838cafab --- /dev/null +++ b/appsec/tests/extension/push_params_block_02.phpt @@ -0,0 +1,47 @@ +--TEST-- +Push address gets blocked even when within a hook +--INI-- +extension=ddtrace.so +datadog.appsec.enabled=1 +--FILE-- + '404', 'type' => 'html']]], ['{"found":"attack"}','{"another":"attack"}']])), +]); +rinit(); + +class SomeIntegration { + public function init() + { + DDTrace\install_hook("ltrim", self::hooked_function(), null); + } + + private static function hooked_function() + { + return static function (DDTrace\HookData $hook) { + stop_for_debugger(); + push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]); + var_dump("This should be executed"); + }; + } +} + +$integration = new SomeIntegration(); +$integration->init(); +echo PHP_EOL; +var_dump(ltrim(" Calling wrapped function")); +var_dump("THIS SHOULD NOT GET IN THE OUTPUT"); + +?> +--EXPECTHEADERS-- +Status: 404 Not Found +Content-type: text/html; charset=UTF-8 +--EXPECTF-- +You've been blocked

Sorry, you cannot access this page. Please contact the customer service team.

diff --git a/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp b/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp index 927756503f..35c6e249ba 100644 --- a/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp @@ -14,12 +14,11 @@ namespace dds { namespace mock = remote_config::mock; -auto &ASM_FEATURES = remote_config::known_products::ASM_FEATURES; remote_config::config get_config_with_status(std::string status) { return mock::get_config( - ASM_FEATURES, "{\"asm\":{\"enabled\":" + status + "}}"); + "ASM_FEATURES", "{\"asm\":{\"enabled\":" + status + "}}"); } remote_config::config get_enabled_config(bool as_string = true) @@ -130,7 +129,7 @@ TEST(RemoteConfigAsmFeaturesListener, std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; remote_config::config non_base_64_content_config = - mock::get_config(ASM_FEATURES, invalid_content); + mock::get_config("ASM_FEATURES", invalid_content); try { listener.on_update(non_base_64_content_config); @@ -153,7 +152,7 @@ TEST(RemoteConfigAsmFeaturesListener, remote_config::asm_features_listener listener(remote_config_service); std::string invalid_content = "invalidJsonContent"; remote_config::config config = - mock::get_config(ASM_FEATURES, invalid_content); + mock::get_config("ASM_FEATURES", invalid_content); try { listener.on_update(config); @@ -175,7 +174,7 @@ TEST(RemoteConfigAsmFeaturesListener, ListenerThrowsAnErrorWhenAsmKeyMissing) auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); remote_config::config asm_key_missing = - mock::get_config(ASM_FEATURES, "{}"); + mock::get_config("ASM_FEATURES", "{}"); try { listener.on_update(asm_key_missing); @@ -196,7 +195,7 @@ TEST(RemoteConfigAsmFeaturesListener, ListenerThrowsAnErrorWhenAsmIsNotValid) auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); remote_config::config invalid_asm_key = - mock::get_config(ASM_FEATURES, "{ \"asm\": 123}"); + mock::get_config("ASM_FEATURES", "{ \"asm\": 123}"); try { listener.on_update(invalid_asm_key); @@ -218,7 +217,7 @@ TEST( auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); remote_config::config enabled_key_missing = - mock::get_config(ASM_FEATURES, "{ \"asm\": {}}"); + mock::get_config("ASM_FEATURES", "{ \"asm\": {}}"); try { listener.on_update(enabled_key_missing); @@ -240,7 +239,7 @@ TEST(RemoteConfigAsmFeaturesListener, auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); remote_config::config enabled_key_invalid = - mock::get_config(ASM_FEATURES, "{ \"asm\": { \"enabled\": 123}}"); + mock::get_config("ASM_FEATURES", "{ \"asm\": { \"enabled\": 123}}"); try { listener.on_update(enabled_key_invalid); diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp index d04f80dc39..43c0974819 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp @@ -60,7 +60,7 @@ TEST(RemoteConfigAsmAggregator, EmptyConfigThrows) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, {})), + EXPECT_THROW(aggregator.add(get_config("ASM", {})), std::runtime_error); // mmap failure aggregator.aggregate(doc); @@ -92,7 +92,7 @@ TEST(RemoteConfigAsmAggregator, IncorrectTypeThrows) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, rule_override)), + EXPECT_THROW(aggregator.add(get_config("ASM", rule_override)), remote_config::error_applying_config); aggregator.aggregate(doc); @@ -122,7 +122,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideEmpty) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -151,7 +151,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideSingleConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -194,10 +194,10 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideMultipleConfigs) rapidjson::Document doc(rapidjson::kObjectType); remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, rule_override)); - aggregator.add(get_config(known_products::ASM, rule_override)); - aggregator.add(get_config(known_products::ASM, rule_override)); - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -242,15 +242,15 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideIgnoreInvalidConfigs) rapidjson::Document doc(rapidjson::kObjectType); remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, rule_override)); - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); { const std::string invalid = R"({"rules_override": {"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -296,7 +296,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverridesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -336,9 +336,9 @@ TEST(RemoteConfigAsmAggregator, RulesOverridesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, rule_override)); - aggregator.add(get_config(known_products::ASM, rule_override)); - aggregator.add(get_config(known_products::ASM, rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -385,7 +385,7 @@ TEST(RemoteConfigAsmAggregator, ActionsSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -414,9 +414,9 @@ TEST(RemoteConfigAsmAggregator, ActionsMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, action_definitions)); - aggregator.add(get_config(known_products::ASM, action_definitions)); - aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -445,15 +445,15 @@ TEST(RemoteConfigAsmAggregator, ActionsIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); { const std::string invalid = R"({"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(get_config(known_products::ASM, action_definitions)); - aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -483,9 +483,9 @@ TEST(RemoteConfigAsmAggregator, ActionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, action_definitions)); - aggregator.add(get_config(known_products::ASM, action_definitions)); - aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -508,7 +508,7 @@ TEST(RemoteConfigAsmAggregator, ActionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -538,7 +538,7 @@ TEST(RemoteConfigAsmAggregator, ExclusionsSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -567,10 +567,10 @@ TEST(RemoteConfigAsmAggregator, ExclusionsMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -599,15 +599,15 @@ TEST(RemoteConfigAsmAggregator, ExclusionsIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); { const std::string invalid = R"({"exclusions": {"id":1,"rules_target":[{"rule_id":1}]}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -637,10 +637,10 @@ TEST(RemoteConfigAsmAggregator, ExclusionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -663,7 +663,7 @@ TEST(RemoteConfigAsmAggregator, ExclusionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -693,7 +693,7 @@ TEST(RemoteConfigAsmAggregator, CustomRulesSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -722,15 +722,15 @@ TEST(RemoteConfigAsmAggregator, CustomRulesMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); { const std::string invalid = R"({"custom_rules": {"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -759,10 +759,10 @@ TEST(RemoteConfigAsmAggregator, CustomRulesIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -792,10 +792,10 @@ TEST(RemoteConfigAsmAggregator, CustomRulesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -818,7 +818,7 @@ TEST(RemoteConfigAsmAggregator, CustomRulesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -848,7 +848,7 @@ TEST(RemoteConfigAsmAggregator, AllSingleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -877,23 +877,23 @@ TEST(RemoteConfigAsmAggregator, IgnoreInvalidConfigs) { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}]})"; - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, update)), + EXPECT_THROW(aggregator.add(get_config("ASM", update)), remote_config::error_applying_config); } { const std::string update = R"({"custom_rules":[{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}]})"; - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); } aggregator.aggregate(doc); @@ -923,23 +923,23 @@ TEST(RemoteConfigAsmAggregator, IgnoreInvalidOverlappingConfigs) { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}],"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}],"custom_rules":[{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}]})"; - aggregator.add(get_config(known_products::ASM, update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}],"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, update)), + EXPECT_THROW(aggregator.add(get_config("ASM", update)), remote_config::error_applying_config); } { const std::string update = R"({"custom_rules":{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}})"; - EXPECT_THROW(aggregator.add(get_config(known_products::ASM, update)), + EXPECT_THROW(aggregator.add(get_config("ASM", update)), remote_config::error_applying_config); } aggregator.aggregate(doc); diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp index f9573d6135..776bc51efd 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp @@ -62,7 +62,7 @@ remote_config::config get_rules_data(std::vector data) rapidjson::Writer writer(buffer); document.Accept(writer); - return get_config(known_products::ASM_DATA, buffer.get_string_ref()); + return get_config("ASM_DATA", buffer.get_string_ref()); } TEST(RemoteConfigAsmDataAggregator, ParseRulesData) @@ -335,8 +335,7 @@ TEST(RemoteConfigAsmDataAggregator, IgnoreInvalidConfigs) { const std::string &invalid = R"({"rules_data": [{"id": "id01", "data": [{"expiration": 11, "value": "1.2.3.5"} ], "type": "ip_with_expiration"},{"data": [{"expiration": 11111, "value": "1.2.3.4"} ], "type": "ip_with_expiration"}]})"; - EXPECT_THROW( - aggregator.add(get_config(known_products::ASM_DATA, invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM_DATA", invalid)), remote_config::error_applying_config); } aggregator.aggregate(doc); @@ -415,8 +414,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfContentNotInBase64) { std::string invalid_content = "&&&"; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -439,8 +437,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfContentNotValidJsonContent) { std::string invalid_content = "InvalidJsonContent"; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -465,8 +462,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoRulesDataKey) std::string expected_error_message = "Invalid config json contents: rules_data key missing or " "invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -491,8 +487,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfRulesDataNotArray) std::string expected_error_message = "Invalid config json contents: rules_data key missing or " "invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -516,8 +511,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfRulesDataEntryNotObject) std::string invalid_content = "{\"rules_data\": [\"invalid\"] }"; std::string expected_error_message = "Invalid config json contents: rules_data entry invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -544,8 +538,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoId) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -572,8 +565,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfIdNotString) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -600,8 +592,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoType) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -628,8 +619,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfTypeNotString) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -655,8 +645,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoData) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -683,8 +672,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataNotArray) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -709,8 +697,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataEntryNotObject) R"({"rules_data": [{"data": [ "invalid" ], "id": "some_id", "type": "ip_with_expiration"} ] })"; std::string expected_error_message = "Invalid config json contents: Entry on data not a valid object"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -734,8 +721,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataExpirationHasInvalidType) std::string invalid_content = R"({"rules_data": [{"data": [{"expiration": "invalid", "value": "1.2.3.4"}], "id": "some_id", "type": "data_with_expiration"}]})"; std::string expected_error_message = "Invalid type for expiration entry"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -760,8 +746,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataValueMissing) "{\"rules_data\": [{\"data\": [{\"expiration\": 11} ], \"id\": " "\"some_id\", \"type\": \"data_with_expiration\"} ] }"; std::string expected_error_message = "Invalid value of data entry"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -787,8 +772,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataValueHasInvalidType) "\"value\": 1234} ], \"id\": \"some_id\", \"type\": " "\"ip_with_expiration\"} ] }"; std::string expected_error_message = "Invalid value of data entry"; - remote_config::config config = - get_config(known_products::ASM_DATA, invalid_content); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp index afa9f055f7..1c9dd18f23 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp @@ -24,7 +24,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(get_config(known_products::ASM_DD, waf_rule)); + aggregator.add(get_config("ASM_DD", waf_rule)); aggregator.aggregate(doc); const auto &rules = doc["rules"]; @@ -38,7 +38,7 @@ TEST(RemoteConfigAsmDdAggregator, RemoveConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.remove(get_config(known_products::ASM_DD, waf_rule)); + aggregator.remove(get_config("ASM_DD", waf_rule)); aggregator.aggregate(doc); const auto &rules = doc["rules"]; @@ -55,8 +55,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfigInvalidBase64Content) std::string invalid_content = "&&&"; std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - get_config(known_products::ASM_DD, invalid_content); + remote_config::config config = get_config("ASM_DD", invalid_content); remote_config::asm_dd_aggregator aggregator; rapidjson::Document doc(rapidjson::kObjectType); @@ -79,8 +78,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfigInvalidJsonContent) std::string invalid_content = "InvalidJsonContent"; std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - get_config(known_products::ASM_DD, invalid_content); + remote_config::config config = get_config("ASM_DD", invalid_content); remote_config::asm_dd_aggregator aggregator; rapidjson::Document doc(rapidjson::kObjectType); diff --git a/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp b/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp index 3c75044cc2..1e3a3d4bb1 100644 --- a/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp @@ -57,8 +57,7 @@ TEST(RemoteConfigEngineListener, UnknownConfig) remote_config::engine_listener listener(engine); listener.init(); - EXPECT_THROW( - listener.on_update(get_config(known_products::UNKNOWN, waf_rule)), + EXPECT_THROW(listener.on_update(get_config("UNKNOWN", waf_rule)), error_applying_config); listener.commit(); } @@ -75,7 +74,7 @@ TEST(RemoteConfigEngineListener, RuleUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); listener.commit(); { @@ -105,7 +104,7 @@ TEST(RemoteConfigEngineListener, RuleUpdateFallback) remote_config::engine_listener listener(engine, create_sample_rules_ok()); listener.init(); - listener.on_unapply(get_config(known_products::ASM_DD, waf_rule)); + listener.on_unapply(get_config("ASM_DD", waf_rule)); listener.commit(); { @@ -138,7 +137,7 @@ TEST(RemoteConfigEngineListener, RulesOverrideUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -179,8 +178,8 @@ TEST(RemoteConfigEngineListener, RulesAndRulesOverrideUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -228,7 +227,7 @@ TEST(RemoteConfigEngineListener, ExclusionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -270,8 +269,8 @@ TEST(RemoteConfigEngineListener, RulesAndExclusionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -320,7 +319,7 @@ TEST(RemoteConfigEngineListener, ActionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -363,8 +362,8 @@ TEST(RemoteConfigEngineListener, RulesAndActionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -415,7 +414,7 @@ TEST(RemoteConfigEngineListener, CustomRulesUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -460,8 +459,8 @@ TEST(RemoteConfigEngineListener, RulesAndCustomRulesUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -509,7 +508,7 @@ TEST(RemoteConfigEngineListener, RulesDataUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DATA, update)); + listener.on_update(get_config("ASM_DATA", update)); listener.commit(); { @@ -543,8 +542,8 @@ TEST(RemoteConfigEngineListener, RulesAndRuleDataUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); - listener.on_update(get_config(known_products::ASM_DATA, update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM_DATA", update)); listener.commit(); { @@ -581,11 +580,11 @@ TEST(RemoteConfigEngineListener, FullUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); { const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(get_config(known_products::ASM_DATA, update)); + listener.on_update(get_config("ASM_DATA", update)); } { const std::string update = @@ -594,23 +593,23 @@ TEST(RemoteConfigEngineListener, FullUpdate) {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"actions": [{"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } listener.commit(); @@ -637,11 +636,11 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) remote_config::engine_listener listener(engine, create_sample_rules_ok()); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); { const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(get_config(known_products::ASM_DATA, update)); + listener.on_update(get_config("ASM_DATA", update)); } listener.commit(); @@ -676,12 +675,12 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } listener.commit(); @@ -717,17 +716,17 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) } listener.init(); - listener.on_update(get_config(known_products::ASM_DD, waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); { const std::string update = R"({"actions": [{"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); } listener.commit(); @@ -800,7 +799,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdate) remote_config::engine_listener listener(e); listener.init(); - listener.on_update(get_config(known_products::ASM_DD, new_rules)); + listener.on_update(get_config("ASM_DD", new_rules)); listener.commit(); { @@ -843,7 +842,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdateFallback) remote_config::engine_listener listener(e, create_sample_rules_ok()); listener.init(); - listener.on_unapply(get_config(known_products::ASM_DD, "")); + listener.on_unapply(get_config("ASM_DD", "")); listener.commit(); { @@ -880,7 +879,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideUpdateDisableRule) const std::string rule_override = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(get_config(known_products::ASM, rule_override)); + listener.on_update(get_config("ASM", rule_override)); { auto ctx = engine->get_context(); @@ -929,7 +928,7 @@ TEST(RemoteConfigEngineListener, RuleOverrideUpdateSetOnMatch) const std::string rule_override = R"({"rules_override": [{"rules_target": [{"tags": {"type": "flow1"}}], "on_match": ["block"]}]})"; - listener.on_update(get_config(known_products::ASM, rule_override)); + listener.on_update(get_config("ASM", rule_override)); { auto ctx = engine->get_context(); @@ -982,7 +981,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideAndActionsUpdate) {"status_code": "303", "location": "localhost"}}],"rules_override": [{"rules_target": [{"rule_id": "1"}], "on_match": ["redirect"]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); { auto ctx = engine->get_context(); @@ -1032,7 +1031,7 @@ TEST(RemoteConfigEngineListener, EngineExclusionsUpdatePasslistRule) const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); { auto ctx = engine->get_context(); @@ -1093,7 +1092,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) "category":"custom"},"conditions":[{"operator":"match_regex","parameters": {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(get_config(known_products::ASM, update)); + listener.on_update(get_config("ASM", update)); { auto ctx = engine->get_context(); @@ -1137,8 +1136,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) } listener.init(); - listener.on_update( - get_config(known_products::ASM, R"({"custom_rules":[]})")); + listener.on_update(get_config("ASM", R"({"custom_rules":[]})")); listener.commit(); { @@ -1190,7 +1188,7 @@ TEST(RemoteConfigEngineListener, EngineRuleDataUpdate) const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(get_config(known_products::ASM_DATA, update)); + listener.on_update(get_config("ASM_DATA", update)); { auto ctx = e->get_context(); diff --git a/appsec/tests/helper/remote_config/mocks.cpp b/appsec/tests/helper/remote_config/mocks.cpp index c7fc1729a3..6aa0988781 100644 --- a/appsec/tests/helper/remote_config/mocks.cpp +++ b/appsec/tests/helper/remote_config/mocks.cpp @@ -5,7 +5,8 @@ #include namespace dds::remote_config::mock { -remote_config::config get_config(product p, const std::string &content) +remote_config::config get_config( + std::string_view product_name, const std::string &content) { static std::atomic id{0}; @@ -34,7 +35,7 @@ remote_config::config get_config(product p, const std::string &content) ::close(shm_fd); - return {shm_path, std::string{"datadog/2/"} + std::string{p.name()} + + return {shm_path, std::string{"datadog/2/"} + std::string{product_name} + "/foobar_" + std::to_string(cur_id) + "/config"}; } } // namespace dds::remote_config::mock diff --git a/appsec/tests/helper/remote_config/mocks.hpp b/appsec/tests/helper/remote_config/mocks.hpp index b1cbb0d969..f34f1d7701 100644 --- a/appsec/tests/helper/remote_config/mocks.hpp +++ b/appsec/tests/helper/remote_config/mocks.hpp @@ -29,6 +29,7 @@ class engine : public dds::engine { static auto create() { return std::shared_ptr(new engine()); } }; -remote_config::config get_config(product p, const std::string &content); +remote_config::config get_config( + std::string_view product_name, const std::string &content); } // namespace dds::remote_config::mock diff --git a/appsec/tests/integration/src/test/bin/enable_extensions.sh b/appsec/tests/integration/src/test/bin/enable_extensions.sh index ec9674b2bc..3d91dadc5f 100755 --- a/appsec/tests/integration/src/test/bin/enable_extensions.sh +++ b/appsec/tests/integration/src/test/bin/enable_extensions.sh @@ -23,8 +23,8 @@ if [[ -f /appsec/ddappsec.so && -d /project ]]; then echo datadog.appsec.enabled=true echo datadog.appsec.helper_path=/appsec/libddappsec-helper.so echo datadog.appsec.helper_log_file=/tmp/logs/helper.log -# echo datadog.appsec.helper_log_level=info - echo datadog.appsec.helper_log_level=debug + echo datadog.appsec.helper_log_level=info +# echo datadog.appsec.helper_log_level=debug echo datadog.appsec.rules=/etc/recommended.json echo datadog.appsec.log_file=/tmp/logs/appsec.log echo datadog.appsec.log_level=debug From 5866db2078de6a5c5bf0616579c753b9ff9a0918 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 4 Oct 2024 17:06:28 +0100 Subject: [PATCH 5/8] Helper: check shmem against process euid, rather than uid --- appsec/src/helper/remote_config/config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appsec/src/helper/remote_config/config.cpp b/appsec/src/helper/remote_config/config.cpp index d324d6ff74..852c63615d 100644 --- a/appsec/src/helper/remote_config/config.cpp +++ b/appsec/src/helper/remote_config/config.cpp @@ -86,10 +86,10 @@ mapped_memory config::read() const throw std::runtime_error{ "Call to fstat on memory segment failed: " + strerror_ts(errno)}; } - if (shm_stat.st_uid != ::getuid()) { + if (shm_stat.st_uid != ::geteuid()) { throw std::runtime_error{"Shared memory segment does not have the " "expected owner. Expect our uid " + - std::to_string(::getuid()) + " but found " + + std::to_string(::geteuid()) + " but found " + std::to_string(shm_stat.st_uid)}; } From 55faa29bf16ac6412d3d156753b3d6fd9eba6bdc Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:13:09 +0100 Subject: [PATCH 6/8] Remove stray test --- .../tests/extension/push_params_block_02.phpt | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 appsec/tests/extension/push_params_block_02.phpt diff --git a/appsec/tests/extension/push_params_block_02.phpt b/appsec/tests/extension/push_params_block_02.phpt deleted file mode 100644 index 04838cafab..0000000000 --- a/appsec/tests/extension/push_params_block_02.phpt +++ /dev/null @@ -1,47 +0,0 @@ ---TEST-- -Push address gets blocked even when within a hook ---INI-- -extension=ddtrace.so -datadog.appsec.enabled=1 ---FILE-- - '404', 'type' => 'html']]], ['{"found":"attack"}','{"another":"attack"}']])), -]); -rinit(); - -class SomeIntegration { - public function init() - { - DDTrace\install_hook("ltrim", self::hooked_function(), null); - } - - private static function hooked_function() - { - return static function (DDTrace\HookData $hook) { - stop_for_debugger(); - push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]); - var_dump("This should be executed"); - }; - } -} - -$integration = new SomeIntegration(); -$integration->init(); -echo PHP_EOL; -var_dump(ltrim(" Calling wrapped function")); -var_dump("THIS SHOULD NOT GET IN THE OUTPUT"); - -?> ---EXPECTHEADERS-- -Status: 404 Not Found -Content-type: text/html; charset=UTF-8 ---EXPECTF-- -You've been blocked

Sorry, you cannot access this page. Please contact the customer service team.

From 2cbe028ddbf3f1b61442bcce49196a5ae37f2f72 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:56:52 +0100 Subject: [PATCH 7/8] Suppress appsec on certain tracer tests --- tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt | 1 + .../exceptions_and_errors_are_ignored_in_tracing_closure.phpt | 1 + tests/ext/sandbox/exception_is_defined.phpt | 1 + .../exceptions_and_errors_are_ignored_in_tracing_closure.phpt | 1 + tests/ext/sandbox/fatal_errors_ignored_in_shutdown.phpt | 1 + tests/ext/sandbox/fatal_errors_ignored_in_tracing_closure.phpt | 1 + 6 files changed, 6 insertions(+) diff --git a/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt b/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt index fe12ee4637..a5cc003137 100644 --- a/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt +++ b/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt @@ -4,6 +4,7 @@ Errors in ddtrace autoloader do not affect error_get_last() DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_AUTOLOAD_NO_COMPILE=1 +DD_APPSEC_ENABLED=0 --INI-- error_reporting=E_ALL datadog.trace.sources_path="{PWD}/.." diff --git a/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt b/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt index 61a3447334..75d2109bcb 100644 --- a/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt +++ b/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt @@ -3,6 +3,7 @@ --ENV-- DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=mt_rand,mt_srand +DD_APPSEC_ENABLED=0 --INI-- error_reporting=E_ALL --FILE-- diff --git a/tests/ext/sandbox/exception_is_defined.phpt b/tests/ext/sandbox/exception_is_defined.phpt index 8909a92771..364354d7f0 100644 --- a/tests/ext/sandbox/exception_is_defined.phpt +++ b/tests/ext/sandbox/exception_is_defined.phpt @@ -2,6 +2,7 @@ Exceptions in the tracing closure callback are always defined --ENV-- DD_TRACE_TRACED_INTERNAL_FUNCTIONS=array_sum +DD_APPSEC_ENABLED=0 --FILE-- Date: Mon, 7 Oct 2024 14:55:16 +0100 Subject: [PATCH 8/8] Fix Appsec integration tests --- .../com/datadog/appsec/php/docker/AppSecContainer.groovy | 1 + .../com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy | 4 +++- .../com/datadog/appsec/php/integration/CommonTests.groovy | 2 +- appsec/tests/integration/src/test/www/laravel8x/initialize.sh | 2 ++ appsec/tests/integration/src/test/www/symfony62/initialize.sh | 2 ++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy index 6a0928d299..605f8450f5 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy @@ -76,6 +76,7 @@ class AppSecContainer> extends GenericContain withEnv 'DD_TRACE_LOG_LEVEL', 'info,startup=off' withEnv 'DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS', '0' withEnv 'DD_TRACE_AGENT_FLUSH_INTERVAL', '0' + withEnv 'DD_TRACE_SIDECAR_TRACE_SENDER', '0' withEnv 'DD_TRACE_DEBUG', '1' withEnv 'DD_AUTOLOAD_NO_COMPILE', 'true' // must be exactly 'true' withEnv 'DD_TRACE_GIT_METADATA_ENABLED', '0' diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy index 72026e3614..b6882c8dc3 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy @@ -101,7 +101,9 @@ class TracesV04Handler implements Handler { List traces = [] MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(is) while (unpacker.hasNext()) { - traces << MsgpackHelper.unpackSingle(unpacker) + def trace = MsgpackHelper.unpackSingle(unpacker) + log.debug('Read submitted trace {}', trace) + traces << trace } traces.first() as List ?: [] diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy index 2f0a815046..84ca54e07a 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy @@ -392,7 +392,7 @@ trait CommonTests { void 'module does not have STATIC_TLS flag'() { Container.ExecResult res = container.execInContainer( 'bash', '-c', - '''! { readelf -d "$(php -r 'echo ini_get("extension_dir");')"/ddappsec.so | grep STATIC_TLS; }''') + '''! { readelf -d "$(DD_TRACE_CLI_ENABLED=0 php -r 'echo ini_get("extension_dir");')"/ddappsec.so | grep STATIC_TLS; }''') if (res.exitCode != 0) { throw new AssertionError("Module has STATIC_TLS flag: $res.stdout") } diff --git a/appsec/tests/integration/src/test/www/laravel8x/initialize.sh b/appsec/tests/integration/src/test/www/laravel8x/initialize.sh index 778669352a..327e27be49 100755 --- a/appsec/tests/integration/src/test/www/laravel8x/initialize.sh +++ b/appsec/tests/integration/src/test/www/laravel8x/initialize.sh @@ -2,6 +2,8 @@ cd /var/www +export DD_TRACE_CLI_ENABLED=false + composer install --no-dev chown -R www-data.www-data vendor diff --git a/appsec/tests/integration/src/test/www/symfony62/initialize.sh b/appsec/tests/integration/src/test/www/symfony62/initialize.sh index 59254e3bc2..6e66797719 100755 --- a/appsec/tests/integration/src/test/www/symfony62/initialize.sh +++ b/appsec/tests/integration/src/test/www/symfony62/initialize.sh @@ -2,6 +2,8 @@ cd /var/www +export DD_TRACE_CLI_ENABLED=false + composer install php bin/console doctrine:database:drop --force php bin/console doctrine:database:create