Skip to content

Commit

Permalink
appsec helper: make http calls timeout
Browse files Browse the repository at this point in the history
asio synchronous network APIs don't support timeouts. Switch to async
APIs with stackless C++20 coroutine executor.
  • Loading branch information
cataphract committed Jul 11, 2024
1 parent bb71831 commit dbeb811
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 19 deletions.
8 changes: 5 additions & 3 deletions appsec/cmake/helper.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ list(FILTER HELPER_SOURCE EXCLUDE REGEX "^.*main\.cpp$")

add_library(helper_objects OBJECT ${HELPER_SOURCE})
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_compile_definitions(helper_objects PUBLIC SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE)
Expand All @@ -27,16 +29,16 @@ target_link_libraries(ddappsec-helper helper_objects) # for its PUBLIC deps

try_compile(STDLIBXX_FS_NO_LIB_NEEDED ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_fslib.cpp
CXX_STANDARD 17
CXX_STANDARD 20
CXX_STANDARD_REQUIRED TRUE)
try_compile(STDLIBXX_FS_NEEDS_STDCXXFS ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_fslib.cpp
CXX_STANDARD 17
CXX_STANDARD 20
CXX_STANDARD_REQUIRED TRUE
LINK_LIBRARIES stdc++fs)
try_compile(STDLIBXX_FS_NEEDS_CXXFS ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_fslib.cpp
CXX_STANDARD 17
CXX_STANDARD 20
CXX_STANDARD_REQUIRED TRUE
LINK_LIBRARIES c++fs)
if(NOT STDLIBXX_FS_NO_LIB_NEEDED)
Expand Down
66 changes: 52 additions & 14 deletions appsec/src/helper/remote_config/http_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
// This product includes software developed at Datadog
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
#include "http_api.hpp"
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/this_coro.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <exception>
#include <future>
#include <optional>
#include <spdlog/spdlog.h>
#include <string>
Expand All @@ -18,29 +25,35 @@ namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>

static const int version = 11;
namespace {
constexpr auto timeout =
std::chrono::duration_cast<net::steady_timer::duration>(
std::chrono::seconds{60});
const int version = 11;

std::string execute_request(const std::string &host, const std::string &port,
const http::request<http::string_body> &request)
net::awaitable<std::string> execute_request(const std::string &host,
const std::string &port, const http::request<http::string_body> &request)
{
std::string result;

try {
// The io_context is required for all I/O
net::io_context ioc;
auto exec = co_await net::this_coro::executor;

// These objects perform our I/O
tcp::resolver resolver(ioc);
beast::tcp_stream stream(ioc);
tcp::resolver resolver(exec);
beast::tcp_stream stream(exec);

// Look up the domain name
auto const results = resolver.resolve(host, port);
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
stream.connect(results);
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
http::write(stream, request);
co_await http::async_write(stream, request, net::use_awaitable);

// This buffer is used for reading and must be persisted
beast::flat_buffer buffer;
Expand All @@ -49,7 +62,7 @@ std::string execute_request(const std::string &host, const std::string &port,
http::response<http::dynamic_body> res;

// Receive the HTTP response
http::read(stream, buffer, res);
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());
Expand All @@ -75,16 +88,41 @@ std::string execute_request(const std::string &host, const std::string &port,
"Connection error - " + err + " - " + e.what());
}

return result;
co_return result;
}

std::string execute_request_sync(const std::string &host,
const std::string &port, const http::request<http::string_body> &req)
{

net::io_context ioc;
net::awaitable<std::string> client_coroutine =
execute_request(host, port, req);

std::promise<std::string> 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<http::string_body> 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(host_, port_, req);
return execute_request_sync(host_, port_, req);
}

std::string dds::remote_config::http_api::get_configs(
Expand All @@ -103,5 +141,5 @@ std::string dds::remote_config::http_api::get_configs(
req.body() = std::move(request);
req.keep_alive(true);

return execute_request(host_, port_, req);
return execute_request_sync(host_, port_, req);
};
7 changes: 5 additions & 2 deletions appsec/tests/fuzzer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSIO
set_target_properties(RapidJSON::rapidjson PROPERTIES INTERFACE_COMPILE_DEFINITIONS "RAPIDJSON_HAS_STDSTRING=1")

add_executable(ddappsec_helper_fuzzer ${HELPER_SOURCE} main.cpp mutators.cpp)
set_target_properties(ddappsec_helper_fuzzer PROPERTIES COMPILE_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping")
set_target_properties(ddappsec_helper_fuzzer PROPERTIES LINK_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping")
set_target_properties(ddappsec_helper_fuzzer PROPERTIES
COMPILE_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping"
LINK_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping"
CXX_STANDARD 20
)
target_include_directories(ddappsec_helper_fuzzer PRIVATE ${HELPER_INCLUDE_DIR})

execute_process(
Expand Down

0 comments on commit dbeb811

Please sign in to comment.