April 21th 2024 update

This commit is contained in:
Ned Wright
2024-04-21 12:38:24 +00:00
parent 189c9209c9
commit 66ed4a8d81
73 changed files with 994 additions and 1166 deletions

View File

@@ -1,5 +1,5 @@
ADD_DEFINITIONS(-Wno-deprecated-declarations -Dalpine)
add_library(orchestration_downloader curl_client.cc downloader.cc http_client.cc https_client.cc https_client_helper.cc)
add_library(orchestration_downloader curl_client.cc downloader.cc https_client.cc https_client_helper.cc)
#add_subdirectory(downloader_ut)

View File

@@ -15,7 +15,7 @@
#include "i_orchestration_tools.h"
#include "singleton.h"
#include "http_client.h"
#include "https_client.h"
#include "debug.h"
#include "config.h"
#include "rest.h"
@@ -63,7 +63,7 @@ class Downloader::Impl : Singleton::Provide<I_Downloader>::From<Downloader>
public:
void init();
Maybe<string> downloadFileFromFog(
Maybe<string> downloadFile(
const string &checksum,
Package::ChecksumTypes checksum_type,
const GetResourceFile &resourse_file
@@ -87,7 +87,7 @@ public:
string getProfileFromMap(const string &tenant_id) const override;
private:
Maybe<string> downloadFileFromFogByHTTP(
Maybe<string> downloadAttributeFile(
const GetResourceFile &resourse_file,
const string &file_name
) const;
@@ -130,12 +130,12 @@ Downloader::Impl::init()
}
Maybe<string>
Downloader::Impl::downloadFileFromFog(
Downloader::Impl::downloadFile(
const string &checksum,
Package::ChecksumTypes checksum_type,
const GetResourceFile &resourse_file) const
{
auto file_path = downloadFileFromFogByHTTP(resourse_file, resourse_file.getFileName() + ".download");
auto file_path = downloadAttributeFile(resourse_file, resourse_file.getFileName() + ".download");
if (!file_path.ok()) {
return file_path;
@@ -378,11 +378,11 @@ Downloader::Impl::validateChecksum(
}
Maybe<string>
Downloader::Impl::downloadFileFromFogByHTTP(const GetResourceFile &resourse_file, const string &file_name) const
Downloader::Impl::downloadAttributeFile(const GetResourceFile &resourse_file, const string &file_name) const
{
string file_path = dir_path + "/" + file_name;
dbgInfo(D_ORCHESTRATOR) << "Downloading file from fog. File: " << resourse_file.getFileName();
dbgInfo(D_ORCHESTRATOR) << "Downloading file. File: " << resourse_file.getFileName();
I_UpdateCommunication *update_communication = Singleton::Consume<I_UpdateCommunication>::by<Downloader>();
auto downloaded_file = update_communication->downloadAttributeFile(resourse_file, file_path);
@@ -408,17 +408,14 @@ Downloader::Impl::getFileFromLocal(const string &local_file_path, const string &
Maybe<string>
Downloader::Impl::getFileFromURL(const URLParser &url, const string &file_path, bool auth_required) const
{
ofstream outFile(file_path, ofstream::out | ofstream::binary);
HTTPClient http_client;
dbgInfo(D_ORCHESTRATOR) << "Downloading file. URL: " << url;
auto get_file_response = http_client.getFile(url, outFile, auth_required);
auto get_file_response = HTTPSClient().getFile(url, file_path, auth_required);
if (!get_file_response.ok()) {
Maybe<string> error = genError("Failed to download file from " + url.getBaseURL().unpack() +
". Error: " + get_file_response.getErr());
dbgWarning(D_ORCHESTRATOR) << "Download failed";
return error;
}
outFile.close();
dbgInfo(D_ORCHESTRATOR) << "Download completed. URL: " << url;
return file_path;
}

View File

@@ -38,7 +38,7 @@ TEST_F(DownloaderTest, doNothing)
{
}
TEST_F(DownloaderTest, downloadFileFromFog)
TEST_F(DownloaderTest, downloadFile)
{
string fog_response = "bla bla";
string checksum = "123";
@@ -55,7 +55,7 @@ TEST_F(DownloaderTest, downloadFileFromFog)
EXPECT_CALL(mock_orchestration_tools, isNonEmptyFile("/tmp/virtualSettings.download")).WillOnce(Return(true));
Maybe<string> downloaded_file = i_downloader->downloadFileFromFog(
Maybe<string> downloaded_file = i_downloader->downloadFile(
checksum,
Package::ChecksumTypes::SHA256,
resourse_file
@@ -76,7 +76,7 @@ TEST_F(DownloaderTest, downloadFileFromFogFailure)
downloadAttributeFile(resourse_file, "/tmp/settings.download")
).WillOnce(Return(fog_response));
Maybe<string> downloaded_file = i_downloader->downloadFileFromFog(
Maybe<string> downloaded_file = i_downloader->downloadFile(
checksum,
Package::ChecksumTypes::SHA256,
resourse_file
@@ -241,7 +241,7 @@ TEST_F(DownloaderTest, downloadEmptyFileFromFog)
calculateChecksum(Package::ChecksumTypes::SHA256, "/tmp/manifest.download")
).WillOnce(Return(checksum));
Maybe<string> downloaded_file = i_downloader->downloadFileFromFog(
Maybe<string> downloaded_file = i_downloader->downloadFile(
checksum,
Package::ChecksumTypes::SHA256,
resourse_file

View File

@@ -1,268 +0,0 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "http_client.h"
#include "curl_client.h"
#include "downloader.h"
#include "debug.h"
#include "i_encryptor.h"
#include "url_parser.h"
#include "config.h"
#include "i_environment.h"
#include "orchestration_comp.h"
#include <fstream>
#include <string>
#include <iostream>
#include <chrono>
#include <boost/asio/ip/tcp.hpp>
using boost::asio::ip::tcp;
using namespace std;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
USE_DEBUG_FLAG(D_HTTP_REQUEST);
// LCOV_EXCL_START Reason: Depends on real download server.
class ClientConnection
{
public:
ClientConnection(
const URLParser &_url,
const Maybe<string> &_proxy_url,
const Maybe<uint16_t> &_proxy_port,
const Maybe<string> &_proxy_auth,
const string &_token)
:
url(_url),
proxy_url(_proxy_url),
proxy_port(_proxy_port),
proxy_auth(_proxy_auth),
token(_token)
{
}
Maybe<void>
handleConnect()
{
if (!url.getBaseURL().ok()) {
return genError("Failed to handle connection. Error: " + url.getBaseURL().getErr());
}
string server_name = url.getBaseURL().unpack();
string port = url.getPort();
string query = url.getQuery();
string host = server_name;
try {
if (stoi(port) != 80) {
host = host + ":" + port;
}
} catch (const exception &err) {
return genError("Failed to parse port to a number. Port: " + port );
}
chrono::duration<unsigned int, ratio<1>> sleep_time(60);
io_stream.expires_from_now(sleep_time);
if (proxy_url.ok()) {
if (!proxy_port.ok()) {
return genError(
"Failed to handle connection to server. proxy domain is defined with invalid port, Error: " +
proxy_port.getErr()
);
}
io_stream.connect(proxy_url.unpack(), to_string(proxy_port.unpack()));
} else {
io_stream.connect(server_name, port);
}
if (!io_stream) {
return genError("Failed to handle connection to server. Error: " + io_stream.error().message());
}
string request_url = query;
if (proxy_url.ok()) {
request_url = host + query;
}
stringstream http_request;
http_request << "GET http://" << request_url << " HTTP/1.1\r\n";
http_request << "Host: " << host << "\r\n";
if (!token.empty()) {
http_request << "Authorization: " << "Bearer " << token << "\r\n";
}
http_request << "User-Agent: Infinity Next (a7030abf93a4c13)\r\n";
auto i_trace_env = Singleton::Consume<I_Environment>::by<OrchestrationComp>();
http_request << i_trace_env->getCurrentHeaders();
http_request << "Accept: */*\r\n";
if (proxy_url.ok()) {
http_request << "Accept-Encoding: identity";
http_request << "Connection: close\r\n";
http_request << "Proxy-Connection: Keep-Alive\r\n";
if (proxy_auth.ok()) {
I_Encryptor *encryptor = Singleton::Consume<I_Encryptor>::by<Downloader>();
http_request << "Proxy-Authorization: Basic " + encryptor->base64Encode(proxy_auth.unpack()) + "\r\n";
}
http_request << "\r\n";
} else {
http_request << "Connection: close\r\n\r\n";
}
dbgTrace(D_HTTP_REQUEST) << "Sending the following HTTP Request: " << endl << http_request.str();
io_stream << http_request.str();
return Maybe<void>();
}
Maybe<void>
handleResponse(ofstream &out_file)
{
string response_http_version;
io_stream >> response_http_version;
unsigned int status_code;
io_stream >> status_code;
string status_message;
getline(io_stream, status_message);
if (!io_stream || response_http_version.substr(0, 5) != "HTTP/") {
return genError("Invalid response");
}
if (status_code != 200) {
return genError("HTTP response returned with status code " + status_code);
}
string header;
vector<string> headers;
while (getline(io_stream, header) && header != "\r") {
headers.push_back(header);
}
out_file << io_stream.rdbuf();
dbgTrace(D_HTTP_REQUEST)
<< "Received HTTP Response with the following data (downloaded file will not be printed):"
<< endl
<< response_http_version
<< " "
<< status_code
<< " "
<< status_message
<< endl
<< makeSeparatedStr(headers, "\n");
return Maybe<void>();
}
private:
const URLParser url;
const Maybe<string> proxy_url;
const Maybe<uint16_t> proxy_port;
const Maybe<string> proxy_auth;
const string &token;
boost::asio::ip::tcp::iostream io_stream;
};
Maybe<void>
HTTPClient::getFile(const URLParser &url, ofstream &out_file, bool auth_required)
{
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<HTTPClient>();
auto load_env_proxy = proxy_config->loadProxy();
if (!load_env_proxy.ok()) return load_env_proxy;
string token = "";
if (auth_required) {
token = Singleton::Consume<I_AgentDetails>::by<HTTPClient>()->getAccessToken();
}
if (url.isOverSSL()) {
if (getFileSSLDirect(url, out_file, token).ok()) return Maybe<void>();
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over SSL directly. Trying indirectly.";
if (getFileSSL(url, out_file, token).ok()) return Maybe<void>();
//CURL fallback
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over SSL. Trying via CURL (SSL).";
return curlGetFileOverSSL(url, out_file, token);
}
auto get_file_http_res = getFileHttp(url, out_file, token);
if (!get_file_http_res.ok())
{
//CURL fallback
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over HTTP. Trying via CURL (HTTP).";
return curlGetFileOverHttp(url, out_file, token);
}
return get_file_http_res;
}
Maybe<void>
HTTPClient::curlGetFileOverHttp(const URLParser &url, ofstream &out_file, const string &token)
{
try {
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<HTTPClient>();
HttpCurl http_curl_client(
url,
out_file,
token,
proxy_config->getProxyDomain(ProxyProtocol::HTTPS),
proxy_config->getProxyPort(ProxyProtocol::HTTPS),
proxy_config->getProxyAuthentication(ProxyProtocol::HTTPS));
http_curl_client.setCurlOpts();
bool connection_ok = http_curl_client.connect();
if (!connection_ok)
{
stringstream url_s;
url_s << url;
string err_msg = string("Failed to get file over HTTP. URL: ") + url_s.str();
return genError(err_msg);
}
// As this class is a temporal solution catch all exception types is enabled.
} catch (const exception &e) {
string err_msg = "Failed to get file over HTTP. Exception: " + string(e.what());
return genError(err_msg);
}
return Maybe<void>();
}
Maybe<void>
HTTPClient::getFileHttp(const URLParser &url, ofstream &out_file, const string &token)
{
try {
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<HTTPClient>();
ClientConnection client_connection(
url,
proxy_config->getProxyDomain(ProxyProtocol::HTTP),
proxy_config->getProxyPort(ProxyProtocol::HTTP),
proxy_config->getProxyAuthentication(ProxyProtocol::HTTP),
token
);
auto handle_connect_res = client_connection.handleConnect();
if (!handle_connect_res.ok()) return handle_connect_res;
return client_connection.handleResponse(out_file);
// As this class is a temporal solution catch all exception types is enabled.
} catch (const exception &e) {
string err_msg = "Failed to get file over HTTP. Exception: " + string(e.what());
return genError(err_msg);
}
return Maybe<void>();
}
// LCOV_EXCL_STOP

View File

@@ -1,44 +0,0 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __HTTP_CLIENT_H__
#define __HTTP_CLIENT_H__
#include <string>
#include "maybe_res.h"
#include "url_parser.h"
#include "i_agent_details.h"
#include "i_proxy_configuration.h"
// LCOV_EXCL_START Reason: Depends on real download server.
class HTTPClient
:
public Singleton::Consume<I_AgentDetails>,
public Singleton::Consume<I_ProxyConfiguration>
{
public:
HTTPClient() = default;
Maybe<void> getFile(const URLParser &url, std::ofstream &out_file, bool auth_required);
private:
std::string loadCAChainDir();
Maybe<void> getFileSSL(const URLParser &url, std::ofstream &out_file, const std::string &_token);
Maybe<void> getFileSSLDirect(const URLParser &url, std::ofstream &out_file, const std::string &_token);
Maybe<void> getFileHttp(const URLParser &url, std::ofstream &out_file, const std::string &_token);
Maybe<void> curlGetFileOverHttp(const URLParser &url, std::ofstream &out_file, const std::string &_token);
Maybe<void> curlGetFileOverSSL(const URLParser &url, std::ofstream &out_file, const std::string &_token);
};
// LCOV_EXCL_STOP
#endif // __HTTP_CLIENT_H__

View File

@@ -11,498 +11,51 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "http_client.h"
#include "curl_client.h"
#include "debug.h"
#include "i_agent_details.h"
#include "i_encryptor.h"
#include "downloader.h"
#include "config.h"
#include "boost/uuid/uuid.hpp"
#include "boost/uuid/uuid_generators.hpp"
#include <boost/asio/deadline_timer.hpp>
#include "boost/uuid/uuid_io.hpp"
#include "https_client.h"
#include <fstream>
#include <string>
#include <iostream>
#include <istream>
#include <ostream>
#include <fstream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/asio/ssl.hpp>
#include <exception>
#include <chrono>
#include "config.h"
#include "curl_client.h"
using namespace boost::placeholders;
using boost::asio::ip::tcp;
using namespace std;
USE_DEBUG_FLAG(D_COMMUNICATION);
USE_DEBUG_FLAG(D_HTTP_REQUEST);
USE_DEBUG_FLAG(D_ORCHESTRATOR);
USE_DEBUG_FLAG(D_HTTP_REQUEST);
// LCOV_EXCL_START Reason: Depends on real download server.
class BadResponseFromServer : public exception
Maybe<void>
HTTPSClient::getFile(const URLParser &url, const string &out_file, bool auth_required)
{
public:
BadResponseFromServer() : message("Bad response returned from server") {}
BadResponseFromServer(const string &msg) : message(msg) {}
const char *
what() const throw()
{
return message.c_str();
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<OrchestrationComp>();
auto load_env_proxy = proxy_config->loadProxy();
if (!load_env_proxy.ok()) return load_env_proxy;
string token = "";
if (auth_required) {
token = Singleton::Consume<I_AgentDetails>::by<OrchestrationComp>()->getAccessToken();
}
private:
string message;
};
if (!url.isOverSSL()) return genError("URL is not over SSL.");
class Client
{
public:
Client(
ofstream &out_file,
boost::asio::io_service &io_service,
boost::asio::ssl::context &context,
const URLParser &_url,
const Maybe<string> &_proxy_url,
const Maybe<uint16_t> &_proxy_port,
const Maybe<string> &_proxy_auth,
const string &_token)
:
out_file(out_file),
url(_url),
proxy_url(_proxy_url),
proxy_port(_proxy_port),
proxy_auth(_proxy_auth),
resolver_(io_service),
deadline(io_service),
socket_(io_service),
ssl_socket(socket_, context),
token(_token)
{
}
if (getFileSSLDirect(url, out_file, token).ok()) return Maybe<void>();
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over SSL directly. Trying indirectly.";
Maybe<void>
handleConnection()
{
ostream request_stream(&request_);
stringstream http_request;
http_request << "GET " << url.getQuery() << " HTTP/1.1\r\n";
string host = url.getHost();
string port = url.getPort();
int port_int;
try {
port_int = stoi(port);
} catch (const exception &err) {
dbgWarning(D_COMMUNICATION)
<< "Failed to convert port number from string. Port: "
<< port
<< ", Error: "
<< err.what();
return genError("Failed to parse port to a number. Port: " + port);
}
if (port_int != 443) {
host = host + ":" + port;
}
if (getFileSSL(url, out_file, token).ok()) return Maybe<void>();
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over SSL. Trying via CURL (SSL).";
http_request << "Host: " << host << "\r\n";
if (!token.empty()) {
http_request << "Authorization: " << "Bearer " << token << "\r\n";
}
http_request << "User-Agent: Infinity Next (a7030abf93a4c13)\r\n";
boost::uuids::uuid correlation_id;
try {
correlation_id = uuid_random_gen();
} catch (const boost::uuids::entropy_error &) {
dbgWarning(D_COMMUNICATION) << "Failed to generate random correlation id - entropy exception";
}
http_request << "X-Trace-Id: " + boost::uuids::to_string(correlation_id) + "\r\n";
http_request << "Accept: */*\r\n";
http_request << "Connection: close\r\n\r\n";
request_stream << http_request.str();
deadline.expires_from_now(boost::posix_time::minutes(5));
deadline.async_wait(boost::bind(&Client::checkDeadline, this, _1));
if (proxy_url.ok()) {
if (!proxy_port.ok()) {
dbgWarning(D_COMMUNICATION)
<< "Failed to connect to proxy due to invalid port value, Error: "
<< proxy_port.getErr();
return genError(
"Failed to handle connection to server. proxy port is invalid, Error: " +
proxy_port.getErr()
);
}
if (port_int == 443) host = host + ":" + port;
ostream connect_request_stream(&connect_request);
stringstream proxy_request;
proxy_request << "CONNECT " << host << " HTTP/1.1\r\n";
proxy_request << "Host: " << host << "\r\n";
if (proxy_auth.ok()) {
I_Encryptor *encryptor = Singleton::Consume<I_Encryptor>::by<Downloader>();
proxy_request
<< "Proxy-Authorization: Basic "
<< encryptor->base64Encode(proxy_auth.unpack())
<< "\r\n";
}
proxy_request << "\r\n";
dbgTrace(D_HTTP_REQUEST) << "Connecting to proxy: " << endl << proxy_request.str();
connect_request_stream << proxy_request.str();
tcp::resolver::query query(proxy_url.unpack(), to_string(proxy_port.unpack()));
resolver_.async_resolve(
query,
boost::bind(
&Client::overProxyResolver,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator
)
);
} else {
tcp::resolver::query query(url.getBaseURL().unpack(), port);
resolver_.async_resolve(
query,
boost::bind(
&Client::handleResolve,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator
)
);
}
dbgTrace(D_HTTP_REQUEST) << "Sending the following HTTP Request: " << endl << http_request.str();
return Maybe<void>();
}
private:
void
checkDeadline(const boost::system::error_code &err)
{
if (err) return;
if (deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now()) {
boost::system::error_code ignored_ec = boost::asio::error::operation_aborted;
socket_.close(ignored_ec);
deadline.expires_at(boost::posix_time::pos_infin);
return;
}
deadline.async_wait(boost::bind(&Client::checkDeadline, this, _1));
}
void
overProxyResolver(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
if (!err) {
boost::asio::async_connect(socket_, endpoint_iterator,
boost::bind(&Client::overProxyHandleConnect, this,
boost::asio::placeholders::error));
} else {
string err_msg = "Failed to connect to proxy. Error: " + err.message();
throw BadResponseFromServer(err_msg);
}
}
void
overProxyHandleConnect(const boost::system::error_code &err)
{
if (!err) {
boost::asio::async_write(socket_, connect_request,
boost::bind(&Client::overProxyHandleWriteRequest, this,
boost::asio::placeholders::error));
} else {
string err_msg = "Failed to connect to proxy. Error: " + err.message();
throw BadResponseFromServer(err_msg);
}
}
void
overProxyHandleWriteRequest(const boost::system::error_code &err)
{
if (!err) {
boost::asio::async_read_until(
socket_,
response_,
"\r\n",
boost::bind(&Client::overProxyHandleReadStatusLine, this, boost::asio::placeholders::error)
);
} else {
string err_msg = "Failed to write over proxy. Error: " + err.message();
throw BadResponseFromServer(err_msg);
}
}
void
overProxyHandleReadStatusLine(const boost::system::error_code &err)
{
if (err) {
string err_msg = "Failed to read status line over proxy. Error: " + err.message();
throw BadResponseFromServer(err_msg);
}
// Check that response is OK.
istream response_stream(&response_);
string response_http_version;
response_stream >> response_http_version;
unsigned int status_code;
response_stream >> status_code;
string status_message;
getline(response_stream, status_message);
if (!response_stream || response_http_version.substr(0, 5) != "HTTP/") {
throw BadResponseFromServer("Invalid response");
return;
}
if (status_code != 200) {
string err_msg = "Response returned with status code " + status_code;
throw BadResponseFromServer(err_msg);
}
dbgTrace(D_HTTP_REQUEST)
<< "Received HTTP Response over proxied connection with the following data:"
<< endl
<< response_http_version
<< " "
<< status_code
<< " "
<< status_message;
if (getProfileAgentSettingWithDefault<bool>(false, "agent.config.message.ignoreSslValidation") == false) {
ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert);
ssl_socket.set_verify_callback(boost::bind(&Client::verifyCertificate, this, _1, _2));
} else {
dbgWarning(D_HTTP_REQUEST) << "Ignoring SSL validation";
}
ssl_socket.async_handshake(
boost::asio::ssl::stream_base::client,
boost::bind(&Client::handleHandshake, this, boost::asio::placeholders::error)
);
}
void
handleResolve(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
if (!err) {
boost::asio::async_connect(ssl_socket.lowest_layer(), endpoint_iterator,
boost::bind(&Client::handleConnect, this,
boost::asio::placeholders::error));
} else {
string message = "Failed to connect. Error: " + err.message();
throw BadResponseFromServer(message);
}
}
bool
verifyCertificate(bool preverified, boost::asio::ssl::verify_context &ctx)
{
if (!token.empty()) {
X509_STORE_CTX *cts = ctx.native_handle();
switch (X509_STORE_CTX_get_error(cts))
{
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
dbgWarning(D_ORCHESTRATOR) << "SSL verification error: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT";
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
dbgWarning(D_ORCHESTRATOR) << "SSL verification error: Certificate not yet valid";
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
dbgWarning(D_ORCHESTRATOR) << "Certificate expired";
break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
dbgDebug(D_ORCHESTRATOR) << "Self signed certificate in chain";
if (getConfigurationWithDefault(false, "orchestration", "Self signed certificates acceptable")) {
preverified = true;
}
break;
default:
if (!preverified) {
dbgWarning(D_ORCHESTRATOR)
<< "Certificate verification error number: "
<< X509_STORE_CTX_get_error(cts);
}
break;
}
return preverified;
}
return true;
}
void
handleConnect(const boost::system::error_code &err)
{
if (!err) {
if (getProfileAgentSettingWithDefault<bool>(false, "agent.config.message.ignoreSslValidation") == false) {
ssl_socket.set_verify_mode(
boost::asio::ssl::verify_peer |
boost::asio::ssl::verify_fail_if_no_peer_cert
);
ssl_socket.set_verify_callback(boost::bind(&Client::verifyCertificate, this, _1, _2));
} else {
dbgWarning(D_HTTP_REQUEST) << "Ignoring SSL validation";
}
ssl_socket.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&Client::handleHandshake, this,
boost::asio::placeholders::error));
} else {
string err_message = "Failed to connect. Error: " + err.message();
throw BadResponseFromServer(err_message);
}
}
void
handleHandshake(const boost::system::error_code &error)
{
if (!error) {
boost::asio::buffer_cast<const char*>(request_.data());
boost::asio::async_write(ssl_socket, request_,
boost::bind(&Client::handleWriteRequest, this,
boost::asio::placeholders::error));
} else {
string err_message = "Handshake failed. Error: " + error.message();
throw BadResponseFromServer(err_message);
}
}
void
handleWriteRequest(const boost::system::error_code &err)
{
if (!err) {
boost::asio::async_read_until(ssl_socket, resp, "\r\n",
boost::bind(&Client::handleReadStatusLine, this,
boost::asio::placeholders::error));
} else {
string err_message = "Failed to handle write request. Error: " + err.message();
throw BadResponseFromServer(err_message);
}
}
void
handleReadStatusLine(const boost::system::error_code &err)
{
if (!err) {
istream response_stream(&resp);
string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
string status_message;
getline(response_stream, status_message);
dbgTrace(D_HTTP_REQUEST)
<< "Received HTTP Response with the following data:"
<< endl
<< http_version
<< " "
<< status_code;
if (!response_stream || http_version.substr(0, 5) != "HTTP/") {
string err_message = "Invalid response";
throw BadResponseFromServer(err_message);
}
if (status_code != 200) {
string err_message = "HTTPS response returned with status code " + to_string(status_code)
+ ". URL: " + url.toString();
throw BadResponseFromServer(err_message);
}
boost::asio::async_read_until(ssl_socket, resp, "\r\n\r\n",
boost::bind(&Client::handleReadHeaders, this,
boost::asio::placeholders::error));
} else {
dbgWarning(D_COMMUNICATION) << "Failed to read response status. Error:" << err.message();
string err_message = "Failed to read status. Error: " + err.message();
throw BadResponseFromServer(err_message);
}
}
void
handleReadHeaders(const boost::system::error_code &err)
{
if (!err) {
// Process the response headers.
istream response_stream(&resp);
string header;
vector<string> headers;
while (getline(response_stream, header) && header != "\r") {
headers.push_back(header);
}
dbgTrace(D_HTTP_REQUEST) << "Received Response headers:" << endl << makeSeparatedStr(headers, "\n");
// Write whatever content we already have to output.
if (resp.size() > 0)
out_file << &resp;
// Start reading remaining data until EOF.
boost::asio::async_read(ssl_socket, resp,
boost::asio::transfer_at_least(1),
boost::bind(&Client::handleReadContent, this,
boost::asio::placeholders::error));
} else {
dbgWarning(D_COMMUNICATION) << "Failed to read response headers. Error:" << err.message();
string err_message = "Failed to read headers. Error: " + err.message();
throw BadResponseFromServer(err_message);
}
}
void
handleReadContent(const boost::system::error_code &err)
{
if (!err) {
// Write all of the data that has been read so far.
out_file << &resp;
// Continue reading remaining data until EOF.
boost::asio::async_read(
ssl_socket,
resp,
boost::asio::transfer_at_least(1),
boost::bind(&Client::handleReadContent, this, boost::asio::placeholders::error)
);
} else if (err != boost::asio::error::eof && err != boost::asio::ssl::error::stream_truncated) {
dbgWarning(D_COMMUNICATION) << "Failed to read response body. Error:" << err.message();
string err_message = "Failed to read content. Error: " + err.message();
throw BadResponseFromServer(err_message);
} else if (err == boost::asio::ssl::error::stream_truncated) {
dbgError(D_COMMUNICATION) << "Had SSL warning during reading response body stage. Error:" << err.message();
deadline.cancel();
} else {
deadline.cancel();
}
}
ofstream &out_file;
const URLParser &url;
const Maybe<string> proxy_url;
const Maybe<uint16_t> proxy_port;
const Maybe<string> proxy_auth;
tcp::resolver resolver_;
boost::asio::deadline_timer deadline;
boost::asio::ip::tcp::socket socket_;
boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> ssl_socket;
boost::asio::streambuf request_;
boost::asio::streambuf connect_request;
boost::asio::streambuf response_;
boost::asio::streambuf resp;
const string &token;
boost::uuids::random_generator uuid_random_gen;
};
//CURL fallback
return curlGetFileOverSSL(url, out_file, token);
}
string
HTTPClient::loadCAChainDir()
HTTPSClient::loadCAChainDir()
{
string ca_chain_dir;
auto agent_details = Singleton::Consume<I_AgentDetails>::by<Downloader>();
auto agent_details = Singleton::Consume<I_AgentDetails>::by<OrchestrationComp>();
auto load_ca_chain_dir = agent_details->getOpenSSLDir();
if (load_ca_chain_dir.ok()) {
ca_chain_dir = load_ca_chain_dir.unpack();
@@ -511,69 +64,26 @@ HTTPClient::loadCAChainDir()
}
Maybe<void>
HTTPClient::getFileSSL(const URLParser &url, ofstream &out_file, const string &token)
HTTPSClient::getFileSSL(const URLParser &url, const string &out_file, const string &)
{
try {
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
if (!token.empty()) {
string cert_file_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/certs/fog.pem",
"message",
"Certificate chain file path"
);
dbgTrace(D_ORCHESTRATOR) << "Http client, cert file path: " << cert_file_path;
auto trusted_ca_directory = getConfiguration<string>("message", "Trusted CA directory");
if (trusted_ca_directory.ok() && !trusted_ca_directory.unpack().empty()) {
ctx.add_verify_path(trusted_ca_directory.unpack());
} else {
string cert_file_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/certs/fog.pem",
"message",
"Certificate chain file path"
);
ctx.load_verify_file(cert_file_path);
}
}
boost::asio::io_service io_service;
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<HTTPClient>();
Client client(
out_file,
io_service,
ctx,
url,
proxy_config->getProxyDomain(ProxyProtocol::HTTPS),
proxy_config->getProxyPort(ProxyProtocol::HTTPS),
proxy_config->getProxyAuthentication(ProxyProtocol::HTTPS),
token
);
auto connection_result = client.handleConnection();
if (!connection_result.ok()) {
return connection_result;
};
auto mainloop = Singleton::Consume<I_MainLoop>::by<Downloader>();
while (!io_service.stopped()) {
io_service.poll_one();
mainloop->yield(true);
}
} catch (const exception &e) {
dbgWarning(D_COMMUNICATION) << "Failed to get file over HTTPS. Error:" << string(e.what());
string error_str = "Failed to get file over HTTPS, exception: " + string(e.what());
return genError(error_str);
auto downlaod_file = Singleton::Consume<I_Messaging>::by<OrchestrationComp>()->downloadFile(
HTTPMethod::GET,
url.getQuery(),
out_file
);
if (!downlaod_file.ok()) {
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over SSL. Error: " << downlaod_file.getErr().toString();
return genError(downlaod_file.getErr().toString());
}
return Maybe<void>();
}
Maybe<void>
HTTPClient::curlGetFileOverSSL(const URLParser &url, ofstream &out_file, const string &token)
HTTPSClient::curlGetFileOverSSL(const URLParser &url, const string &out_file, const string &token)
{
try {
string cert_file_path;
if (!token.empty())
{
if (!token.empty()) {
cert_file_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/certs/fog.pem",
"message",
@@ -581,11 +91,12 @@ HTTPClient::curlGetFileOverSSL(const URLParser &url, ofstream &out_file, const s
);
}
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<HTTPClient>();
auto proxy_config = Singleton::Consume<I_ProxyConfiguration>::by<OrchestrationComp>();
ofstream out_file_stream(out_file, ofstream::out | ofstream::binary);
HttpsCurl ssl_curl_client(
url,
out_file,
out_file_stream,
token,
proxy_config->getProxyDomain(ProxyProtocol::HTTPS),
proxy_config->getProxyPort(ProxyProtocol::HTTPS),
@@ -594,8 +105,7 @@ HTTPClient::curlGetFileOverSSL(const URLParser &url, ofstream &out_file, const s
ssl_curl_client.setCurlOpts();
bool connection_ok = ssl_curl_client.connect();
if (!connection_ok)
{
if (!connection_ok) {
stringstream url_s;
url_s << url;
string err_msg = string("Failed to get file over HTTPS. URL: ") + url_s.str();
@@ -603,12 +113,11 @@ HTTPClient::curlGetFileOverSSL(const URLParser &url, ofstream &out_file, const s
}
} catch (const exception &e) {
dbgWarning(D_COMMUNICATION) << "Failed to get file over HTTPS. Error:" << string(e.what());
dbgWarning(D_HTTP_REQUEST) << "Failed to get file over HTTPS. Error:" << string(e.what());
string error_str = "Failed to get file over HTTPS, exception: " + string(e.what());
return genError(error_str);
}
return Maybe<void>();
}
// LCOV_EXCL_STOP

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __HTTPS_CLIENT_H__
#define __HTTPS_CLIENT_H__
#include <string>
#include "maybe_res.h"
#include "url_parser.h"
#include "orchestration_comp.h"
// LCOV_EXCL_START Reason: Depends on real download server.
class HTTPSClient
{
public:
Maybe<void> getFile(const URLParser &url, const std::string &out_file, bool auth_required);
private:
std::string loadCAChainDir();
Maybe<void> getFileSSL(const URLParser &url, const std::string &out_file, const std::string &_token);
Maybe<void> getFileSSLDirect(const URLParser &url, const std::string &out_file, const std::string &_token);
Maybe<void> curlGetFileOverSSL(const URLParser &url, const std::string &out_file, const std::string &_token);
};
// LCOV_EXCL_STOP
#endif // __HTTPS_CLIENT_H__

View File

@@ -11,10 +11,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "http_client.h"
#include "https_client.h"
Maybe<void>
HTTPClient::getFileSSLDirect(const URLParser &, std::ofstream &, const std::string &)
HTTPSClient::getFileSSLDirect(const URLParser &, const std::string &, const std::string &)
{
return genError("No direct downloading in open-source");
}