Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add server certificate validation to wazuh agent #444

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions etc/config/wazuh-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ agent:
thread_count: 4
server_url: https://localhost:27000
retry_interval: 30s
verification_mode: full
events:
batch_interval: 10s
batch_size: 1MB
Expand Down
12 changes: 12 additions & 0 deletions src/agent/communicator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")

add_library(Communicator src/communicator.cpp src/http_client.cpp src/http_request_params.cpp)

if(WIN32)
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_win.cpp")
else()
set(VERIFY_UTILS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/https_socket_verify_utils_lin.cpp")
endif()

target_sources(Communicator PRIVATE ${VERIFY_UTILS_FILE})

target_include_directories(Communicator PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
SYSTEM PRIVATE
Expand All @@ -23,6 +31,10 @@ target_include_directories(Communicator PUBLIC
target_compile_definitions(Communicator PRIVATE -DJWT_DISABLE_PICOJSON=ON)
target_link_libraries(Communicator PUBLIC Config Boost::asio Boost::beast Boost::system Boost::url Logger PRIVATE OpenSSL::SSL OpenSSL::Crypto nlohmann_json::nlohmann_json)

if(WIN32)
target_link_libraries(Communicator PRIVATE Crypt32)
endif()

include(../../cmake/ConfigureTarget.cmake)
configure_target(Communicator)

Expand Down
16 changes: 16 additions & 0 deletions src/agent/communicator/include/communicator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ namespace communicator
LogWarn("batch_size must be between 1KB and 100MB. Using default value.");
m_batchSize = config::agent::DEFAULT_BATCH_SIZE;
}

m_verificationMode = getConfigValue.template operator()<std::string>("agent", "verification_mode")
.value_or(config::agent::DEFAULT_VERIFICATION_MODE);

if (std::find(std::begin(config::agent::VALID_VERIFICATION_MODES),
std::end(config::agent::VALID_VERIFICATION_MODES),
m_verificationMode) == std::end(config::agent::VALID_VERIFICATION_MODES))
{
LogWarn("Incorrect value for 'verification_mode', in case of HTTPS connections the default value '{}' "
"is used.",
config::agent::DEFAULT_VERIFICATION_MODE);
m_verificationMode = config::agent::DEFAULT_VERIFICATION_MODE;
}
}

/// @brief Sends an authentication request to the manager
Expand Down Expand Up @@ -149,5 +162,8 @@ namespace communicator

/// @brief Timer to wait for token expiration
std::unique_ptr<boost::asio::steady_timer> m_tokenExpTimer;

/// @brief The verification mode
std::string m_verificationMode;
};
} // namespace communicator
8 changes: 6 additions & 2 deletions src/agent/communicator/include/http_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,26 @@ namespace http_client
/// @param userAgent User agent header
/// @param uuid Unique user identifier
/// @param key Authentication key
/// @param verificationMode Verification mode
/// @return Authentication token if successful, otherwise nullopt
std::optional<std::string> AuthenticateWithUuidAndKey(const std::string& serverUrl,
const std::string& userAgent,
const std::string& uuid,
const std::string& key) override;
const std::string& key,
const std::string& verificationMode) override;

/// @brief Authenticates using username and password
/// @param serverUrl Server URL for authentication
/// @param userAgent User agent header
/// @param user Username for authentication
/// @param password User password
/// @param verificationMode Verification mode
/// @return Authentication token if successful, otherwise nullopt
std::optional<std::string> AuthenticateWithUserPassword(const std::string& serverUrl,
const std::string& userAgent,
const std::string& user,
const std::string& password) override;
const std::string& password,
const std::string& verificationMode) override;

private:
/// @brief Performs an HTTP request with a response handler
Expand Down
2 changes: 2 additions & 0 deletions src/agent/communicator/include/http_request_params.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace http_client
std::string Port;
std::string Endpoint;
std::string User_agent;
std::string Verification_Mode;
std::string Token;
std::string User_pass;
std::string Body;
Expand All @@ -32,6 +33,7 @@ namespace http_client
const std::string& serverUrl,
std::string endpoint,
std::string userAgent,
std::string verificationMode,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add to list of parameters.

std::string token = "",
std::string userPass = "",
std::string body = "");
Expand Down
17 changes: 17 additions & 0 deletions src/agent/communicator/include/https_socket_verify_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <boost/asio/ssl.hpp>

#include <string>

namespace https_socket_verify_utils
{
/// @brief Verifies the certificate of the HTTPS connection
/// @param preverified The result of the pre-verification
/// @param ctx The verification context
/// @param mode The verification mode to use
/// @param host The hostname to verify against
/// @return True if the certificate is valid, false otherwise
bool VerifyCertificate(bool preverified,
boost::asio::ssl::verify_context& ctx,
const std::string& mode,
const std::string& host);
} // namespace https_socket_verify_utils
8 changes: 6 additions & 2 deletions src/agent/communicator/include/ihttp_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,26 @@ namespace http_client
/// @param userAgent The user agent header
/// @param uuid The UUID
/// @param key The key
/// @param verificationMode The verification mode
/// @return The authentication token
virtual std::optional<std::string> AuthenticateWithUuidAndKey(const std::string& serverUrl,
const std::string& userAgent,
const std::string& uuid,
const std::string& key) = 0;
const std::string& key,
const std::string& verificationMode) = 0;

/// @brief Authenticate with a user and password
/// @param serverUrl The URL of the server
/// @param userAgent The user agent header
/// @param user The user
/// @param password The password
/// @param verificationMode The verification mode
/// @return The authentication token
virtual std::optional<std::string> AuthenticateWithUserPassword(const std::string& serverUrl,
const std::string& userAgent,
const std::string& user,
const std::string& password) = 0;
const std::string& password,
const std::string& verificationMode) = 0;

/// @brief Perform an HTTP request, receive the response and write it to a file
/// @param params The parameters for the request
Expand Down
5 changes: 5 additions & 0 deletions src/agent/communicator/include/ihttp_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ namespace http_client
/// @brief Virtual destructor
virtual ~IHttpSocket() = default;

/// @brief Sets the verification mode for the host
/// @param host The host name
/// @param verificationMode The verification mode to set
virtual void SetVerificationMode(const std::string& host, const std::string& verificationMode) = 0;

/// @brief Connects the socket to the given endpoints
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
Expand Down
16 changes: 11 additions & 5 deletions src/agent/communicator/src/communicator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace communicator
boost::beast::http::status Communicator::SendAuthenticationRequest()
{
const auto token = m_httpClient->AuthenticateWithUuidAndKey(
m_serverUrl, m_getHeaderInfo ? m_getHeaderInfo() : "", m_uuid, m_key);
m_serverUrl, m_getHeaderInfo ? m_getHeaderInfo() : "", m_uuid, m_key, m_verificationMode);

if (token.has_value())
{
Expand Down Expand Up @@ -78,8 +78,11 @@ namespace communicator
return m_keepRunning.load();
};

const auto reqParams = http_client::HttpRequestParams(
boost::beast::http::verb::get, m_serverUrl, "/api/v1/commands", m_getHeaderInfo ? m_getHeaderInfo() : "");
const auto reqParams = http_client::HttpRequestParams(boost::beast::http::verb::get,
m_serverUrl,
"/api/v1/commands",
m_getHeaderInfo ? m_getHeaderInfo() : "",
m_verificationMode);
co_await m_httpClient->Co_PerformHttpRequest(
m_token, reqParams, {}, onAuthenticationFailed, m_retryInterval, m_batchSize, onSuccess, loopCondition);
}
Expand Down Expand Up @@ -156,7 +159,8 @@ namespace communicator
const auto reqParams = http_client::HttpRequestParams(boost::beast::http::verb::post,
m_serverUrl,
"/api/v1/events/stateful",
m_getHeaderInfo ? m_getHeaderInfo() : "");
m_getHeaderInfo ? m_getHeaderInfo() : "",
m_verificationMode);
co_await m_httpClient->Co_PerformHttpRequest(m_token,
reqParams,
getMessages,
Expand Down Expand Up @@ -184,7 +188,8 @@ namespace communicator
const auto reqParams = http_client::HttpRequestParams(boost::beast::http::verb::post,
m_serverUrl,
"/api/v1/events/stateless",
m_getHeaderInfo ? m_getHeaderInfo() : "");
m_getHeaderInfo ? m_getHeaderInfo() : "",
m_verificationMode);
co_await m_httpClient->Co_PerformHttpRequest(m_token,
reqParams,
getMessages,
Expand Down Expand Up @@ -223,6 +228,7 @@ namespace communicator
"/api/v1/files?file_name=" + groupName +
config::DEFAULT_SHARED_FILE_EXTENSION,
m_getHeaderInfo ? m_getHeaderInfo() : "",
m_verificationMode,
*m_token);

const auto result = m_httpClient->PerformHttpRequestDownload(reqParams, dstFilePath);
Expand Down
35 changes: 29 additions & 6 deletions src/agent/communicator/src/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ namespace http_client
continue;
}

if (reqParams.Use_Https)
{
socket->SetVerificationMode(reqParams.Host, reqParams.Verification_Mode);
}

boost::system::error_code ec;

co_await socket->AsyncConnect(results, ec);
Expand Down Expand Up @@ -247,11 +252,18 @@ namespace http_client
std::optional<std::string> HttpClient::AuthenticateWithUuidAndKey(const std::string& serverUrl,
const std::string& userAgent,
const std::string& uuid,
const std::string& key)
const std::string& key,
const std::string& verificationMode)
{
const std::string body = R"({"uuid":")" + uuid + R"(", "key":")" + key + "\"}";
const auto reqParams = http_client::HttpRequestParams(
boost::beast::http::verb::post, serverUrl, "/api/v1/authentication", userAgent, "", "", body);
const auto reqParams = http_client::HttpRequestParams(boost::beast::http::verb::post,
serverUrl,
"/api/v1/authentication",
userAgent,
verificationMode,
"",
"",
body);

const auto res = PerformHttpRequest(reqParams);

Expand Down Expand Up @@ -300,7 +312,8 @@ namespace http_client
std::optional<std::string> HttpClient::AuthenticateWithUserPassword(const std::string& serverUrl,
const std::string& userAgent,
const std::string& user,
const std::string& password)
const std::string& password,
const std::string& verificationMode)
{
std::string basicAuth {};
std::string userPass {user + ":" + password};
Expand All @@ -309,8 +322,13 @@ namespace http_client

boost::beast::detail::base64::encode(&basicAuth[0], userPass.c_str(), userPass.size());

const auto reqParams = http_client::HttpRequestParams(
boost::beast::http::verb::post, serverUrl, "/security/user/authenticate", userAgent, "", basicAuth);
const auto reqParams = http_client::HttpRequestParams(boost::beast::http::verb::post,
serverUrl,
"/security/user/authenticate",
userAgent,
verificationMode,
"",
basicAuth);

const auto res = PerformHttpRequest(reqParams);

Expand Down Expand Up @@ -363,6 +381,11 @@ namespace http_client
throw std::runtime_error("Failed to create socket.");
}

if (params.Use_Https)
{
socket->SetVerificationMode(params.Host, params.Verification_Mode);
}

boost::system::error_code ec;

socket->Connect(results, ec);
Expand Down
6 changes: 4 additions & 2 deletions src/agent/communicator/src/http_request_params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ namespace http_client
const std::string& serverUrl,
std::string endpoint,
std::string userAgent,
std::string verificationMode,
std::string token,
std::string userPass,
std::string body)
: Method(method)
, Endpoint(std::move(endpoint))
, User_agent(std::move(userAgent))
, Verification_Mode(std::move(verificationMode))
, Token(std::move(token))
, User_pass(std::move(userPass))
, Body(std::move(body))
Expand Down Expand Up @@ -49,7 +51,7 @@ namespace http_client
bool HttpRequestParams::operator==(const HttpRequestParams& other) const
{
return Method == other.Method && Host == other.Host && Port == other.Port && Endpoint == other.Endpoint &&
User_agent == other.User_agent && Token == other.Token && User_pass == other.User_pass &&
Body == other.Body && Use_Https == other.Use_Https;
User_agent == other.User_agent && Verification_Mode == other.Verification_Mode && Token == other.Token &&
User_pass == other.User_pass && Body == other.Body && Use_Https == other.Use_Https;
}
} // namespace http_client
9 changes: 9 additions & 0 deletions src/agent/communicator/src/http_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ namespace http_client
{
}

/// @brief Sets the verification mode for the host
/// @param host The host name
/// @param verificationMode The verification mode to set
void SetVerificationMode([[maybe_unused]] const std::string& host,
[[maybe_unused]] const std::string& verificationMode) override
{
// No functionality for HTTP sockets
}

/// @brief Connects the socket to the given endpoints
/// @param endpoints The endpoints to connect to
/// @param ec The error code, if any occurred
Expand Down
37 changes: 36 additions & 1 deletion src/agent/communicator/src/https_socket.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <https_socket_verify_utils.hpp>
#include <ihttp_socket.hpp>
#include <logger.hpp>

Expand All @@ -21,7 +22,41 @@ namespace http_client
: m_ctx(boost::asio::ssl::context::sslv23)
, m_ssl_socket(io_context, m_ctx)
{
m_ctx.set_verify_mode(boost::asio::ssl::verify_peer);
}

/// @brief Set the verification mode of the HTTPS connection
/// @param host The host name to be verified
/// @param verificationMode The verification mode to use
/// @details The verification modes supported are:
/// - "full": verifies the identity of the server, including the hostname
/// - "certificate": only verifies the identity of the server, without checking the hostname
/// - "none": no verification is performed
void SetVerificationMode(const std::string& host, const std::string& verificationMode) override
{
if (verificationMode == "none")
{
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_none);
return;
}

m_ctx.set_default_verify_paths();
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer);
if (verificationMode == "certificate")
{
m_ssl_socket.set_verify_callback(
[verificationMode, host](bool preverified, boost::asio::ssl::verify_context& ctx)
{ return https_socket_verify_utils::VerifyCertificate(preverified, ctx, verificationMode, host); });
}
else
{
if (verificationMode != "full")
{
LogWarn("Verification mode unknown, full mode is used.");
}
m_ssl_socket.set_verify_callback(
[verificationMode, host](bool preverified, boost::asio::ssl::verify_context& ctx)
{ return https_socket_verify_utils::VerifyCertificate(preverified, ctx, "full", host); });
}
}

/// @brief Connects the socket to the given endpoints
Expand Down
Loading
Loading