mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-30 03:34:26 +03:00
First release of open-appsec source code
This commit is contained in:
5
components/security_apps/orchestration/downloader/CMakeLists.txt
Executable file
5
components/security_apps/orchestration/downloader/CMakeLists.txt
Executable file
@@ -0,0 +1,5 @@
|
||||
ADD_DEFINITIONS(-Wno-deprecated-declarations -Dalpine)
|
||||
|
||||
add_library(orchestration_downloader curl_client.cc downloader.cc http_client.cc https_client.cc)
|
||||
|
||||
add_subdirectory(downloader_ut)
|
438
components/security_apps/orchestration/downloader/curl_client.cc
Executable file
438
components/security_apps/orchestration/downloader/curl_client.cc
Executable file
@@ -0,0 +1,438 @@
|
||||
// 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 "curl_client.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#if defined(alpine)
|
||||
#include <openssl/ossl_typ.h>
|
||||
#else
|
||||
#include <openssl/types.h>
|
||||
#endif //ifdef alpine
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
#include "url_parser.h"
|
||||
#include "debug.h"
|
||||
#include "sasal.h"
|
||||
#include "scope_exit.h"
|
||||
|
||||
USE_DEBUG_FLAG(D_HTTP_REQUEST);
|
||||
|
||||
using namespace std;
|
||||
|
||||
SASAL_START // Orchestration - Communication
|
||||
// LCOV_EXCL_START Reason: Depends on real download server.
|
||||
|
||||
class CurlGlobalInit
|
||||
{
|
||||
public:
|
||||
CurlGlobalInit() { curl_global_init(CURL_GLOBAL_DEFAULT); }
|
||||
~CurlGlobalInit() { curl_global_cleanup(); }
|
||||
};
|
||||
static CurlGlobalInit global_curl_handle;
|
||||
|
||||
HttpCurl::HttpCurl(
|
||||
const URLParser &_url,
|
||||
ofstream &_out_file,
|
||||
const string &_bearer,
|
||||
const Maybe<string> &proxy_url,
|
||||
const Maybe<uint16_t> &proxy_port,
|
||||
const Maybe<string> &proxy_auth)
|
||||
:
|
||||
url(_url),
|
||||
out_file(_out_file),
|
||||
bearer(_bearer),
|
||||
curl(unique_ptr<CURL, function<void(CURL *)>>(curl_easy_init(), curl_easy_cleanup))
|
||||
{
|
||||
string port = url.getPort();
|
||||
if (!port.empty())
|
||||
{
|
||||
curl_url = url.getBaseURL().unpack() + ":" + port + url.getQuery();
|
||||
} else
|
||||
{
|
||||
curl_url = url.getBaseURL().unpack() + url.getQuery();
|
||||
}
|
||||
|
||||
if (proxy_url.ok())
|
||||
{
|
||||
//Update curl proxy
|
||||
if (!proxy_port.ok())
|
||||
{
|
||||
dbgWarning(D_HTTP_REQUEST)
|
||||
<< "Invalid proxy port, CURL default port will be used instead. Error: "
|
||||
<< proxy_port.getErr();
|
||||
proxy = proxy_url.unpack();
|
||||
} else
|
||||
{
|
||||
proxy = proxy_url.unpack() + ":" + to_string(proxy_port.unpack());
|
||||
}
|
||||
}
|
||||
if (proxy_auth.ok())
|
||||
{
|
||||
I_Encryptor *encryptor = Singleton::Consume<I_Encryptor>::by<HttpCurl>();
|
||||
proxy_credentials = "Proxy-Authorization: Basic " + encryptor->base64Encode(proxy_auth.unpack());
|
||||
}
|
||||
}
|
||||
|
||||
HttpCurl::HttpCurl(const HttpCurl &other)
|
||||
:
|
||||
url(other.url),
|
||||
out_file(other.out_file),
|
||||
bearer(other.bearer),
|
||||
proxy(other.proxy),
|
||||
proxy_credentials(other.proxy_credentials),
|
||||
curl(unique_ptr<CURL, function<void(CURL *)>>(curl_easy_init(), curl_easy_cleanup))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
HttpCurl::setCurlOpts(long timeout, HTTP_VERSION http_version)
|
||||
{
|
||||
struct curl_slist *headers = NULL;
|
||||
struct curl_slist *proxy_headers = NULL;
|
||||
CURL *curl_handle = curl.get();
|
||||
|
||||
//HTTP options
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTP_VERSION, http_version);
|
||||
|
||||
//SSL options
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
|
||||
//Header options
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, curl_url.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeResponseCallback);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &out_file);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, timeout);
|
||||
headers = curl_slist_append(headers, "Accept: */*");
|
||||
string auth = string("Authorization: Bearer ") + bearer;
|
||||
headers = curl_slist_append(headers, auth.c_str());
|
||||
headers = curl_slist_append(headers, "User-Agent: Infinity Next (a7030abf93a4c13)");
|
||||
string uuid_header = string("X-Trace-Id: ") + TraceIdGenerator::generateTraceId();
|
||||
headers = curl_slist_append(headers, "Connection: close");
|
||||
headers = curl_slist_append(headers, uuid_header.c_str());
|
||||
|
||||
//Proxy options
|
||||
if (!proxy.empty())
|
||||
{
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PROXY, proxy.c_str());
|
||||
if (!proxy_credentials.empty())
|
||||
{
|
||||
proxy_headers = curl_slist_append(proxy_headers, proxy_credentials.c_str());
|
||||
//Apply proxy headers
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PROXYHEADER, proxy_headers);
|
||||
}
|
||||
dbgTrace(D_HTTP_REQUEST) << "Using Proxy: " << proxy;
|
||||
}
|
||||
|
||||
//Apply headers
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
|
||||
bool
|
||||
HttpCurl::connect()
|
||||
{
|
||||
// Response information.
|
||||
long http_code;
|
||||
char errorstr[CURL_ERROR_SIZE];
|
||||
CURLcode res;
|
||||
stringstream response_header;
|
||||
|
||||
CURL *curl_handle = curl.get();
|
||||
|
||||
auto __scope_exit = make_scope_exit(
|
||||
[this] () {
|
||||
out_file.flush();
|
||||
out_file.close();
|
||||
}
|
||||
);
|
||||
|
||||
//Debug options
|
||||
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_DEBUGFUNCTION, trace_http_request);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_DEBUGDATA, static_cast<void*>(&response_header));
|
||||
curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errorstr);
|
||||
|
||||
// Perform the request, res will get the return code
|
||||
res = curl_easy_perform(curl_handle);
|
||||
if (res != CURLE_OK) {
|
||||
dbgWarning(D_HTTP_REQUEST) << "Failed to perform CURL request. CURL error " << string(errorstr);
|
||||
dbgWarning(D_HTTP_REQUEST) << "CURL result " + string(curl_easy_strerror(res));
|
||||
print_response_header(response_header);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
if (http_code != 200){
|
||||
dbgWarning(D_HTTP_REQUEST) << "Failed to connect. Error code: " + to_string(http_code);
|
||||
print_response_header(response_header);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
dbgTrace(D_HTTP_REQUEST) << "CURL HTTP request successfully completed.";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
HttpCurl::trace_http_request(
|
||||
CURL *,
|
||||
curl_infotype type,
|
||||
char *data,
|
||||
size_t,
|
||||
void *opq)
|
||||
{
|
||||
stringstream *response_header = static_cast<stringstream *>(opq);
|
||||
switch (type)
|
||||
{
|
||||
case CURLINFO_HEADER_OUT:
|
||||
dbgTrace(D_HTTP_REQUEST)
|
||||
<< "=> Sending the following HTTP request:\n"
|
||||
<< string(data);
|
||||
break;
|
||||
case CURLINFO_HEADER_IN:
|
||||
if (!response_header)
|
||||
{
|
||||
// Should never reach this if. But just in case.
|
||||
// The data will be printed at chunks in this case
|
||||
dbgError(D_HTTP_REQUEST)
|
||||
<< "<= Received the following HTTP response header (should not reach here):\n"
|
||||
<< string(data);
|
||||
} else
|
||||
{
|
||||
// The response header Will be printed at once later after curl_easy_perform.
|
||||
// And after assembling all the response header chunks.
|
||||
*response_header << string(data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u_int
|
||||
HttpCurl::writeResponseCallback(
|
||||
const char *in_buf,
|
||||
uint num_of_messages,
|
||||
uint size_of_data,
|
||||
ostream out_stream)
|
||||
{
|
||||
const unsigned long total_bytes(num_of_messages * size_of_data);
|
||||
out_stream.write(in_buf, total_bytes);
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
void
|
||||
HttpCurl::print_response_header(stringstream &stream)
|
||||
{
|
||||
string line;
|
||||
istringstream header_stream(stream.str());
|
||||
stringstream header_lines;
|
||||
int lines_to_print = 10;
|
||||
int i = 0;
|
||||
|
||||
while (getline(header_stream, line) && i < lines_to_print) {
|
||||
header_lines << line << '\n';
|
||||
++i;
|
||||
}
|
||||
|
||||
dbgWarning(D_HTTP_REQUEST)
|
||||
<< "<= Received the following HTTP response header:\n"
|
||||
<< header_lines.str();
|
||||
}
|
||||
|
||||
HttpsCurl::HttpsCurl(
|
||||
const URLParser &_url,
|
||||
ofstream &_out_file,
|
||||
const string &_bearer,
|
||||
const Maybe<string> &proxy_url,
|
||||
const Maybe<uint16_t> &proxy_port,
|
||||
const Maybe<string> &proxy_auth,
|
||||
const string &_ca_path) :
|
||||
HttpCurl(_url, _out_file, _bearer, proxy_url, proxy_port, proxy_auth),
|
||||
ca_path(_ca_path) {}
|
||||
|
||||
HttpsCurl::HttpsCurl(const HttpsCurl &other) :
|
||||
HttpCurl(other),
|
||||
ca_path(other.ca_path) {}
|
||||
|
||||
void
|
||||
HttpsCurl::setCurlOpts(long timeout, HTTP_VERSION http_version)
|
||||
{
|
||||
struct curl_slist *headers = NULL;
|
||||
struct curl_slist *proxy_headers = NULL;
|
||||
CURL *curl_handle = curl.get();
|
||||
|
||||
URLProtocol protocol = url.getProtocol();
|
||||
if (protocol == URLProtocol::HTTPS)
|
||||
{
|
||||
if (curl_url.find("https://") == string::npos)
|
||||
{
|
||||
//Append https://
|
||||
curl_url = "https://" + curl_url;
|
||||
}
|
||||
}
|
||||
|
||||
//HTTP options
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTP_VERSION, http_version);
|
||||
|
||||
//SSL options
|
||||
if (getProfileAgentSettingWithDefault<bool>(
|
||||
false,
|
||||
"agent.config.message.ignoreSslValidation") == false)
|
||||
{
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_verify_certificate);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_CTX_DATA, static_cast<void*>(&bearer));
|
||||
} else
|
||||
{
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
dbgWarning(D_HTTP_REQUEST) << "Ignoring SSL validation";
|
||||
}
|
||||
|
||||
//Header options
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, curl_url.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeResponseCallback);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &out_file);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, timeout);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_CAINFO, ca_path.c_str());
|
||||
headers = curl_slist_append(headers, "Accept: */*");
|
||||
string auth = string("Authorization: Bearer ") + bearer;
|
||||
headers = curl_slist_append(headers, auth.c_str());
|
||||
headers = curl_slist_append(headers, "User-Agent: Infinity Next (a7030abf93a4c13)");
|
||||
string uuid_header = string("X-Trace-Id: ") + TraceIdGenerator::generateTraceId();
|
||||
headers = curl_slist_append(headers, "Connection: close");
|
||||
headers = curl_slist_append(headers, uuid_header.c_str());
|
||||
|
||||
// Proxy options
|
||||
if (!proxy.empty())
|
||||
{
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PROXY, proxy.c_str());
|
||||
if (!proxy_credentials.empty())
|
||||
{
|
||||
proxy_headers = curl_slist_append(proxy_headers, proxy_credentials.c_str());
|
||||
//Apply proxy headers
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PROXYHEADER, proxy_headers);
|
||||
}
|
||||
dbgTrace(D_HTTP_REQUEST) << "Using Proxy : " << proxy;
|
||||
}
|
||||
|
||||
//Apply headers
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
|
||||
int
|
||||
HttpsCurl::verify_certificate(int preverify_ok, X509_STORE_CTX *ctx)
|
||||
{
|
||||
switch (X509_STORE_CTX_get_error(ctx))
|
||||
{
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
||||
dbgWarning(D_HTTP_REQUEST) << "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_HTTP_REQUEST) << "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_HTTP_REQUEST) << "Certificate expired";
|
||||
break;
|
||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
dbgDebug(D_HTTP_REQUEST) << "Self signed certificate in chain";
|
||||
if (getConfigurationWithDefault(false, "orchestration", "Self signed certificates acceptable")) {
|
||||
preverify_ok = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!preverify_ok) {
|
||||
dbgWarning(D_HTTP_REQUEST)
|
||||
<< "Certificate verification error number: "
|
||||
<< X509_STORE_CTX_get_error(ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return preverify_ok;
|
||||
}
|
||||
|
||||
CURLcode
|
||||
HttpsCurl::ssl_ctx_verify_certificate(CURL *, void *sslctx, void *opq)
|
||||
{
|
||||
SSL_CTX *ctx = (SSL_CTX *) sslctx;
|
||||
string *token_ptr = static_cast<string*>(opq);
|
||||
if(!token_ptr)
|
||||
{
|
||||
dbgWarning(D_HTTP_REQUEST) << "Invalid token (bearer) was received";
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
string token = *token_ptr;
|
||||
|
||||
if (token.empty())
|
||||
{
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(
|
||||
ctx,
|
||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER,
|
||||
verify_certificate
|
||||
);
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
string
|
||||
TraceIdGenerator::generateRandomString(uint length)
|
||||
{
|
||||
string dst(length, 0);
|
||||
static thread_local mt19937 range(random_device{}());
|
||||
|
||||
auto randchar = [&]() -> char
|
||||
{
|
||||
static const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
static const size_t size = (sizeof(charset) - 1);
|
||||
return charset[ range() % size ];
|
||||
};
|
||||
|
||||
generate_n(dst.begin(), length, randchar);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
string
|
||||
TraceIdGenerator::generateTraceId()
|
||||
{
|
||||
string part1 = generateRandomString(8);
|
||||
string part2 = generateRandomString(4);
|
||||
string part3 = generateRandomString(4);
|
||||
string part4 = generateRandomString(4);
|
||||
string part5 = generateRandomString(12);
|
||||
return string(part1 + "-" + part2 + "-" + part3 + "-" + part4 + "-" + part5);
|
||||
}
|
||||
|
||||
SASAL_END
|
115
components/security_apps/orchestration/downloader/curl_client.h
Executable file
115
components/security_apps/orchestration/downloader/curl_client.h
Executable file
@@ -0,0 +1,115 @@
|
||||
// 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 <curl/curl.h>
|
||||
|
||||
#if defined(alpine)
|
||||
#include <openssl/ossl_typ.h>
|
||||
#else
|
||||
#include <openssl/types.h>
|
||||
#endif //ifdef alpine
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
|
||||
#include "i_encryptor.h"
|
||||
#include "scope_exit.h"
|
||||
#include "url_parser.h"
|
||||
#include "sasal.h"
|
||||
|
||||
USE_DEBUG_FLAG(D_HTTP_REQUEST);
|
||||
|
||||
SASAL_START // Orchestration - Communication
|
||||
// LCOV_EXCL_START Reason: Depends on real download server.
|
||||
|
||||
enum class HTTP_VERSION
|
||||
{
|
||||
HTTP_VERSION_NONE = CURL_HTTP_VERSION_NONE, //libcurl will use whatever it thinks fit.
|
||||
HTTP_VERSION_1_0 = CURL_HTTP_VERSION_1_0,
|
||||
HTTP_VERSION_1_1 = CURL_HTTP_VERSION_1_1,
|
||||
HTTP_VERSION_2_0 = CURL_HTTP_VERSION_2_0
|
||||
};
|
||||
|
||||
class TraceIdGenerator
|
||||
{
|
||||
public:
|
||||
static std::string generateTraceId();
|
||||
private:
|
||||
static std::string generateRandomString(uint length);
|
||||
};
|
||||
|
||||
class HttpCurl : public Singleton::Consume<I_Encryptor>
|
||||
{
|
||||
public:
|
||||
HttpCurl(
|
||||
const URLParser &_url,
|
||||
std::ofstream &_out_file,
|
||||
const std::string &_bearer,
|
||||
const Maybe<std::string> &proxy_url,
|
||||
const Maybe<uint16_t> &proxy_port,
|
||||
const Maybe<std::string> &proxy_auth);
|
||||
|
||||
HttpCurl(const HttpCurl &other);
|
||||
|
||||
virtual void setCurlOpts(long timeout = 60L, HTTP_VERSION http_version = HTTP_VERSION::HTTP_VERSION_1_1);
|
||||
virtual bool connect();
|
||||
|
||||
protected:
|
||||
static int trace_http_request(
|
||||
CURL *handle,
|
||||
curl_infotype type,
|
||||
char *data,
|
||||
size_t size,
|
||||
void *userptr);
|
||||
static u_int writeResponseCallback(
|
||||
const char *in_buf,
|
||||
uint num_of_messages,
|
||||
uint size_of_data,
|
||||
std::ostream out_stream);
|
||||
void print_response_header(std::stringstream &stream);
|
||||
|
||||
const URLParser& url;
|
||||
std::ofstream &out_file;
|
||||
std::string bearer;
|
||||
std::string proxy;
|
||||
std::string proxy_credentials;
|
||||
std::unique_ptr<CURL, std::function<void(CURL *)>> curl;
|
||||
std::string curl_url;
|
||||
};
|
||||
|
||||
class HttpsCurl : public HttpCurl
|
||||
{
|
||||
public:
|
||||
HttpsCurl(
|
||||
const URLParser &_url,
|
||||
std::ofstream &_out_file,
|
||||
const std::string &_bearer,
|
||||
const Maybe<std::string> &proxy_url,
|
||||
const Maybe<uint16_t> &proxy_port,
|
||||
const Maybe<std::string> &proxy_auth,
|
||||
const std::string &_ca_path);
|
||||
|
||||
HttpsCurl(const HttpsCurl& other);
|
||||
|
||||
static CURLcode ssl_ctx_verify_certificate(CURL *curl, void *ssl_ctx, void *opq);
|
||||
static int verify_certificate(int preverify_ok, X509_STORE_CTX *ctx);
|
||||
void setCurlOpts(long timeout = 60L, HTTP_VERSION http_version = HTTP_VERSION::HTTP_VERSION_1_1) override;
|
||||
|
||||
private:
|
||||
std::string ca_path;
|
||||
};
|
||||
|
||||
SASAL_END
|
387
components/security_apps/orchestration/downloader/downloader.cc
Executable file
387
components/security_apps/orchestration/downloader/downloader.cc
Executable file
@@ -0,0 +1,387 @@
|
||||
// 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 "downloader.h"
|
||||
|
||||
#include "i_orchestration_tools.h"
|
||||
#include "singleton.h"
|
||||
#include "http_client.h"
|
||||
#include "debug.h"
|
||||
#include "config.h"
|
||||
#include "rest.h"
|
||||
#include "sasal.h"
|
||||
#include "cereal/external/rapidjson/document.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace rapidjson;
|
||||
|
||||
SASAL_START // Orchestration - Communication
|
||||
|
||||
USE_DEBUG_FLAG(D_ORCHESTRATOR);
|
||||
|
||||
class Downloader::Impl : Singleton::Provide<I_Downloader>::From<Downloader>
|
||||
{
|
||||
public:
|
||||
void init();
|
||||
|
||||
Maybe<string> downloadFileFromFog(
|
||||
const string &checksum,
|
||||
Package::ChecksumTypes checksum_type,
|
||||
const GetResourceFile &resourse_file
|
||||
) const override;
|
||||
|
||||
Maybe<map<string, string>> downloadVirtualFileFromFog(
|
||||
const GetResourceFile &resourse_file,
|
||||
Package::ChecksumTypes checksum_type
|
||||
) const override;
|
||||
|
||||
Maybe<string> downloadFileFromURL(
|
||||
const string &url,
|
||||
const string &checksum,
|
||||
Package::ChecksumTypes checksum_type,
|
||||
const string &service_name
|
||||
) const override;
|
||||
|
||||
private:
|
||||
Maybe<string> downloadFileFromFogByHTTP(
|
||||
const GetResourceFile &resourse_file,
|
||||
const string &file_name
|
||||
) const;
|
||||
|
||||
Maybe<string> validateChecksum(
|
||||
const string &checksum,
|
||||
Package::ChecksumTypes checksum_type,
|
||||
Maybe<string> &file_path
|
||||
) const;
|
||||
|
||||
Maybe<string> getFileFromExternalURL(
|
||||
const URLParser &url,
|
||||
const string &file_name,
|
||||
bool auth_required
|
||||
) const;
|
||||
Maybe<string> getFileFromLocal(const string &local_file_path, const string &file_name) const;
|
||||
Maybe<string> getFileFromURL(const URLParser &url, const string &file_name, bool auth_required) const;
|
||||
|
||||
tuple<string, string> splitQuery(const string &query) const;
|
||||
string vectorToPath(const vector<string> &vec) const;
|
||||
string dir_path;
|
||||
};
|
||||
|
||||
void
|
||||
Downloader::Impl::init()
|
||||
{
|
||||
dir_path = getConfigurationWithDefault<string>(
|
||||
"/tmp/orchestration_downloads",
|
||||
"orchestration",
|
||||
"Default file download path"
|
||||
);
|
||||
|
||||
Singleton::Consume<I_OrchestrationTools>::by<Downloader>()->createDirectory(dir_path);
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
Downloader::Impl::downloadFileFromFog(
|
||||
const string &checksum,
|
||||
Package::ChecksumTypes checksum_type,
|
||||
const GetResourceFile &resourse_file) const
|
||||
{
|
||||
auto file_path = downloadFileFromFogByHTTP(resourse_file, resourse_file.getFileName() + ".download");
|
||||
|
||||
if (!file_path.ok()) {
|
||||
return file_path;
|
||||
}
|
||||
|
||||
auto checksum_validation = validateChecksum(checksum, checksum_type, file_path);
|
||||
if (!checksum_validation.ok()) return checksum_validation;
|
||||
|
||||
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
||||
if (!orchestration_tools->isNonEmptyFile(file_path.unpack())) {
|
||||
return genError("Failed to download file " + resourse_file.getFileName());
|
||||
}
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
||||
Maybe<map<string, string>>
|
||||
Downloader::Impl::downloadVirtualFileFromFog(
|
||||
const GetResourceFile &resourse_file,
|
||||
Package::ChecksumTypes) const
|
||||
{
|
||||
static const string tenand_id_key = "tenantId";
|
||||
static const string policy_key = "policy";
|
||||
static const string settings_key = "settings";
|
||||
static const string tenants_key = "tenants";
|
||||
static const string error_text = "error";
|
||||
|
||||
map<string, string> res;
|
||||
I_UpdateCommunication *update_communication = Singleton::Consume<I_UpdateCommunication>::by<Downloader>();
|
||||
auto downloaded_data = update_communication->downloadAttributeFile(resourse_file);
|
||||
if (!downloaded_data.ok()) return downloaded_data.passErr();
|
||||
|
||||
Document document;
|
||||
document.Parse(downloaded_data.unpack().c_str());
|
||||
if (document.HasParseError()) return genError("JSON file is not valid.");
|
||||
|
||||
const Value &tenants_data = document[tenants_key.c_str()];
|
||||
for (Value::ConstValueIterator itr = tenants_data.Begin(); itr != tenants_data.End(); ++itr) {
|
||||
|
||||
auto tenant_id_obj = itr->FindMember(tenand_id_key.c_str());
|
||||
if (tenant_id_obj == itr->MemberEnd()) continue;
|
||||
|
||||
string tenant_id = tenant_id_obj->value.GetString();
|
||||
|
||||
Value::ConstMemberIterator artifact_data = itr->FindMember(policy_key.c_str());
|
||||
if (artifact_data == itr->MemberEnd()) artifact_data = itr->FindMember(settings_key.c_str());
|
||||
|
||||
if (artifact_data != itr->MemberEnd()) {
|
||||
string file_path = dir_path + "/" + resourse_file.getFileName() + "_" + tenant_id + ".download";
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
artifact_data->value.Accept(writer);
|
||||
|
||||
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
||||
if (orchestration_tools->writeFile(buffer.GetString(), file_path)) {
|
||||
res.insert({tenant_id, file_path});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Value::ConstMemberIterator error_data = itr->FindMember(error_text.c_str());
|
||||
if (error_data != itr->MemberEnd()) {
|
||||
dbgDebug(D_ORCHESTRATOR)
|
||||
<< "Failed to download artifact"
|
||||
<< ", Tenant ID: " << tenant_id
|
||||
<< ", Error message: " << error_data->value.FindMember("message")->value.GetString()
|
||||
<< ", Error ID: " << error_data->value.FindMember("messageId")->value.GetString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
Downloader::Impl::downloadFileFromURL(
|
||||
const string &url,
|
||||
const string &checksum,
|
||||
Package::ChecksumTypes checksum_type,
|
||||
const string &service_name) const
|
||||
{
|
||||
dbgDebug(D_ORCHESTRATOR) << "Download file. URL: " << url;
|
||||
|
||||
string new_url = url;
|
||||
bool auth_required = false;
|
||||
auto custom_url = getConfiguration<string>("orchestration", "Custom download url");
|
||||
if (custom_url.ok()) {
|
||||
auto resource_index = url.find_last_of("/");
|
||||
string error_msg = "Failed to parse custom URL. ";
|
||||
if (resource_index == string::npos) {
|
||||
return genError(error_msg + "URL: " + url);
|
||||
}
|
||||
new_url = custom_url.unpack();
|
||||
if (new_url.empty()) {
|
||||
return genError(error_msg + "URL is empty");
|
||||
}
|
||||
if (new_url.back() == '/') {
|
||||
new_url = new_url.substr(0, new_url.size() - 1);
|
||||
}
|
||||
new_url.append(url.substr(resource_index));
|
||||
}
|
||||
// Workaround - only in staging we need to add the auth header
|
||||
static const string jwt_word = "<JWT>";
|
||||
if (new_url.find(jwt_word) != string::npos) {
|
||||
new_url = new_url.substr(jwt_word.length());
|
||||
auth_required = true;
|
||||
}
|
||||
|
||||
URLParser parsed_url(new_url);
|
||||
Maybe<string> base_url = parsed_url.getBaseURL();
|
||||
if (!base_url.ok()) return base_url;
|
||||
Maybe<string> file_path = genError("Empty path");
|
||||
string file_name = service_name + ".download";
|
||||
if (parsed_url.getProtocol() == URLProtocol::LOCAL_FILE) {
|
||||
file_path = getFileFromLocal(base_url.unpack(), file_name);
|
||||
} else {
|
||||
file_path = getFileFromExternalURL(parsed_url, file_name, auth_required);
|
||||
}
|
||||
|
||||
if (!file_path.ok()) {
|
||||
return file_path;
|
||||
}
|
||||
|
||||
auto checksum_validation = validateChecksum(checksum, checksum_type, file_path);
|
||||
if (!checksum_validation.ok()) return checksum_validation;
|
||||
|
||||
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
||||
if (!orchestration_tools->isNonEmptyFile(file_path.unpack())) {
|
||||
return genError("Failed to download file. URL: " + parsed_url.toString());
|
||||
}
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
Downloader::Impl::validateChecksum(
|
||||
const string &checksum,
|
||||
Package::ChecksumTypes checksum_type,
|
||||
Maybe<string> &file_path) const
|
||||
{
|
||||
if (file_path.ok()) {
|
||||
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
||||
Maybe<string> file_checksum = orchestration_tools->calculateChecksum(checksum_type, file_path.unpack());
|
||||
if (!file_checksum.ok() || checksum != file_checksum.unpack()) {
|
||||
orchestration_tools->removeFile(file_path.unpack());
|
||||
if (!file_checksum.ok()) {
|
||||
return genError("Failed to calculate file checksum, with error: " + file_checksum.getErr());
|
||||
}
|
||||
return genError(
|
||||
"The checksum calculation is not as the expected, " +
|
||||
checksum + " != " + file_checksum.unpack()
|
||||
);
|
||||
}
|
||||
}
|
||||
return file_path;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
Downloader::Impl::downloadFileFromFogByHTTP(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();
|
||||
|
||||
I_UpdateCommunication *update_communication = Singleton::Consume<I_UpdateCommunication>::by<Downloader>();
|
||||
auto downloaded_file = update_communication->downloadAttributeFile(resourse_file);
|
||||
if (!downloaded_file.ok()) return genError(downloaded_file.getErr());
|
||||
dbgInfo(D_ORCHESTRATOR) << "Download completed. File: " << resourse_file.getFileName();
|
||||
|
||||
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
||||
if (orchestration_tools->writeFile(downloaded_file.unpack(), file_path)) return file_path;
|
||||
return genError("Failed to write the attribute file. File: " + file_name);
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
Downloader::Impl::getFileFromLocal(const string &local_file_path, const string &file_name) const
|
||||
{
|
||||
string file_path = dir_path + "/" + file_name;
|
||||
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
||||
if (!orchestration_tools->copyFile(local_file_path, file_path)) {
|
||||
return genError("Get file from local failed. File: " + local_file_path);
|
||||
}
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START Reason: Depends on real download server.
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
Downloader::Impl::getFileFromExternalURL(
|
||||
const URLParser &parsed_url,
|
||||
const string &file_name,
|
||||
bool auth_required
|
||||
) const
|
||||
{
|
||||
string file_path = dir_path + "/" + file_name;
|
||||
auto base_url = parsed_url.getBaseURL().unpack();
|
||||
|
||||
string query_path;
|
||||
string query_file;
|
||||
tie(query_path, query_file) = splitQuery(parsed_url.getQuery());
|
||||
|
||||
auto try_dirs = getConfigurationWithDefault<bool>(
|
||||
false,
|
||||
"orchestration",
|
||||
"Add tenant suffix"
|
||||
);
|
||||
if (try_dirs) {
|
||||
vector<string> sub_path;
|
||||
auto agent_details = Singleton::Consume<I_AgentDetails>::by<Downloader>();
|
||||
auto tenant_id = agent_details->getTenantId();
|
||||
if (!tenant_id.empty()) {
|
||||
sub_path.push_back(tenant_id);
|
||||
auto profile_id = agent_details->getProfileId();
|
||||
if (!profile_id.empty()) {
|
||||
sub_path.push_back(profile_id);
|
||||
auto agent_id = agent_details->getAgentId();
|
||||
if(!agent_id.empty()) {
|
||||
sub_path.push_back(agent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
URLParser currentUrl = parsed_url;
|
||||
while (!sub_path.empty()) {
|
||||
currentUrl.setQuery(query_path + vectorToPath(sub_path) + "/" + query_file);
|
||||
if (getFileFromURL(currentUrl, file_path, auth_required).ok()) return file_path;
|
||||
sub_path.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
return getFileFromURL(parsed_url, file_path, auth_required);
|
||||
}
|
||||
|
||||
tuple<string, string>
|
||||
Downloader::Impl::splitQuery(const string &query) const
|
||||
{
|
||||
size_t index = query.find_last_of("/");
|
||||
if (index == string::npos) return make_tuple(string(), query);
|
||||
return make_tuple(query.substr(0, index), query.substr(index + 1));
|
||||
}
|
||||
|
||||
string
|
||||
Downloader::Impl::vectorToPath(const vector<string> &vec) const
|
||||
{
|
||||
string s;
|
||||
for (const auto &piece : vec) { s += ("/" + piece); }
|
||||
return s;
|
||||
}
|
||||
|
||||
Downloader::Downloader() : Component("Downloader"), pimpl(make_unique<Impl>()) {}
|
||||
|
||||
Downloader::~Downloader() {}
|
||||
|
||||
void
|
||||
Downloader::init()
|
||||
{
|
||||
pimpl->init();
|
||||
}
|
||||
|
||||
void
|
||||
Downloader::preload()
|
||||
{
|
||||
registerExpectedConfiguration<string>("orchestration", "Custom download url");
|
||||
registerExpectedConfiguration<string>("orchestration", "Default file download path");
|
||||
registerExpectedConfiguration<string>("orchestration", "Self signed certificates acceptable");
|
||||
registerExpectedConfiguration<bool>("orchestration", "Add tenant suffix");
|
||||
}
|
||||
|
||||
SASAL_END
|
@@ -0,0 +1,7 @@
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(
|
||||
downloader_ut
|
||||
"downloader_ut.cc"
|
||||
"orchestration;orchestration_downloader;orchestration_modules;orchestration_tools;environment;config;update_communication;metric;event_is;-lcurl;-lcrypto;-lssl;-lboost_regex"
|
||||
)
|
431
components/security_apps/orchestration/downloader/downloader_ut/downloader_ut.cc
Executable file
431
components/security_apps/orchestration/downloader/downloader_ut/downloader_ut.cc
Executable file
@@ -0,0 +1,431 @@
|
||||
#include "cptest.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "downloader.h"
|
||||
#include "enum_range.h"
|
||||
#include "environment.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
#include "mock/mock_update_communication.h"
|
||||
#include "mock/mock_orchestration_tools.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class DownloaderTest : public Test
|
||||
{
|
||||
public:
|
||||
DownloaderTest()
|
||||
{
|
||||
setConfiguration<string>("/tmp", "orchestration", "Default file download path");
|
||||
EXPECT_CALL(mock_orchestration_tools, createDirectory("/tmp")).WillOnce(Return(true));
|
||||
downloader.init();
|
||||
}
|
||||
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
::Environment env;
|
||||
ConfigComponent config_component;
|
||||
StrictMock<MockUpdateCommunication> mock_communication;
|
||||
StrictMock<MockOrchestrationTools> mock_orchestration_tools;
|
||||
Downloader downloader;
|
||||
I_Downloader *i_downloader = Singleton::Consume<I_Downloader>::from(downloader);
|
||||
};
|
||||
|
||||
TEST_F(DownloaderTest, doNothing)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, downloadFileFromFog)
|
||||
{
|
||||
string fog_response = "bla bla";
|
||||
string checksum = "123";
|
||||
|
||||
GetResourceFile resourse_file(GetResourceFile::ResourceFileType::VIRTUAL_SETTINGS);
|
||||
|
||||
EXPECT_CALL(mock_communication, downloadAttributeFile(resourse_file)).WillOnce(Return(fog_response));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_orchestration_tools,
|
||||
calculateChecksum(Package::ChecksumTypes::SHA256, "/tmp/virtualSettings.download")
|
||||
).WillOnce(Return(string("123")));
|
||||
|
||||
EXPECT_CALL(mock_orchestration_tools, writeFile(fog_response, "/tmp/virtualSettings.download"))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, isNonEmptyFile("/tmp/virtualSettings.download")).WillOnce(Return(true));
|
||||
|
||||
Maybe<string> downloaded_file = i_downloader->downloadFileFromFog(
|
||||
checksum,
|
||||
Package::ChecksumTypes::SHA256,
|
||||
resourse_file
|
||||
);
|
||||
|
||||
EXPECT_THAT(downloaded_file, IsValue("/tmp/virtualSettings.download"));
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, downloadFileFromFogFailure)
|
||||
{
|
||||
string checksum = "123";
|
||||
|
||||
Maybe<string> fog_response(genError("Failed to download"));
|
||||
GetResourceFile resourse_file(GetResourceFile::ResourceFileType::SETTINGS);
|
||||
|
||||
EXPECT_CALL(mock_communication, downloadAttributeFile(resourse_file)).WillOnce(Return(fog_response));
|
||||
|
||||
Maybe<string> downloaded_file = i_downloader->downloadFileFromFog(
|
||||
checksum,
|
||||
Package::ChecksumTypes::SHA256,
|
||||
resourse_file
|
||||
);
|
||||
|
||||
EXPECT_FALSE(downloaded_file.ok());
|
||||
EXPECT_THAT(downloaded_file, IsError("Failed to download"));
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, registerConfig)
|
||||
{
|
||||
string file_path_value = "/tmp";
|
||||
string signed_certificates_value = "bla";
|
||||
string config_json =
|
||||
"{\n"
|
||||
" \"orchestration\": {\n"
|
||||
" \"Default file download path\": [\n"
|
||||
" {\n"
|
||||
" \"context\": \"All()\",\n"
|
||||
" \"value\": \"" + file_path_value + "\"\n"
|
||||
" }\n"
|
||||
" ],\n"
|
||||
" \"Self signed certificates acceptable\": [\n"
|
||||
" {\n"
|
||||
" \"context\": \"All()\",\n"
|
||||
" \"value\": \"" + signed_certificates_value + "\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
env.preload();
|
||||
env.init();
|
||||
downloader.preload();
|
||||
istringstream stringstream(config_json);
|
||||
Singleton::Consume<Config::I_Config>::from(config_component)->loadConfiguration(stringstream);
|
||||
|
||||
EXPECT_THAT(
|
||||
getConfiguration<string>("orchestration", "Default file download path"),
|
||||
IsValue(file_path_value)
|
||||
);
|
||||
|
||||
EXPECT_THAT(
|
||||
getConfiguration<string>("orchestration", "Self signed certificates acceptable"),
|
||||
IsValue(signed_certificates_value)
|
||||
);
|
||||
|
||||
env.fini();
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, downloadWithBadChecksum)
|
||||
{
|
||||
string local_file_path = "/tmp/test_file.sh";
|
||||
string url = "file://" + local_file_path;
|
||||
string dir_path = getConfigurationWithDefault<string>(
|
||||
"/tmp/orchestration_downloads",
|
||||
"orchestration",
|
||||
"Default file download path"
|
||||
);
|
||||
string service_name = "test";
|
||||
string file_name = service_name + ".download";
|
||||
string file_path = dir_path + "/" + file_name;
|
||||
string checksum = "1234";
|
||||
Package::ChecksumTypes checksum_type = Package::ChecksumTypes::MD5;
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_orchestration_tools,
|
||||
calculateChecksum(checksum_type, file_path)
|
||||
).WillOnce(Return(checksum + "5"));
|
||||
EXPECT_CALL(mock_orchestration_tools, copyFile(local_file_path, file_path)).WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, removeFile("/tmp/test.download")).WillOnce(Return(true));
|
||||
EXPECT_THAT(
|
||||
i_downloader->downloadFileFromURL(url, checksum, checksum_type, service_name),
|
||||
IsError("The checksum calculation is not as the expected, 1234 != 12345")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, downloadFromLocal)
|
||||
{
|
||||
string local_file_path = "/tmp/test_file.sh";
|
||||
string url = "file://" + local_file_path;
|
||||
string dir_path = getConfigurationWithDefault<string>(
|
||||
"/tmp/orchestration_downloads",
|
||||
"orchestration",
|
||||
"Default file download path"
|
||||
);
|
||||
string service_name = "test";
|
||||
string file_name = service_name + ".download";
|
||||
string file_path = dir_path + "/" + file_name;
|
||||
string checksum = "1234";
|
||||
Package::ChecksumTypes checksum_type = Package::ChecksumTypes::MD5;
|
||||
|
||||
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(checksum_type, file_path)).WillOnce(Return(checksum));
|
||||
EXPECT_CALL(mock_orchestration_tools, copyFile(local_file_path, file_path)).WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, isNonEmptyFile(file_path)).WillOnce(Return(true));
|
||||
i_downloader->downloadFileFromURL(url, checksum, checksum_type, service_name);
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, downloadEmptyFileFromFog)
|
||||
{
|
||||
string fog_response = "bla bla";
|
||||
string checksum = "123";
|
||||
string service_name = "manifest";
|
||||
string empty_str = "";
|
||||
|
||||
GetResourceFile resourse_file(GetResourceFile::ResourceFileType::MANIFEST);
|
||||
|
||||
EXPECT_CALL(mock_communication, downloadAttributeFile(resourse_file)).WillOnce(Return(fog_response));
|
||||
|
||||
EXPECT_CALL(mock_orchestration_tools, writeFile(fog_response, "/tmp/manifest.download"))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, isNonEmptyFile("/tmp/manifest.download")).WillOnce(Return(false));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_orchestration_tools,
|
||||
calculateChecksum(Package::ChecksumTypes::SHA256, "/tmp/manifest.download")
|
||||
).WillOnce(Return(checksum));
|
||||
|
||||
Maybe<string> downloaded_file = i_downloader->downloadFileFromFog(
|
||||
checksum,
|
||||
Package::ChecksumTypes::SHA256,
|
||||
resourse_file
|
||||
);
|
||||
|
||||
EXPECT_FALSE(downloaded_file.ok());
|
||||
EXPECT_THAT(downloaded_file, IsError("Failed to download file manifest"));
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, downloadFromCustomURL)
|
||||
{
|
||||
string file_prefix = "file://";
|
||||
string file_name = "/test_file.sh";
|
||||
string local_file_path = "/tmp" + file_name;
|
||||
string url = file_prefix + local_file_path;
|
||||
string custom_URL = "/custom";
|
||||
setConfiguration<string>(
|
||||
string(file_prefix + custom_URL),
|
||||
"orchestration",
|
||||
"Custom download url"
|
||||
);
|
||||
string dir_path = getConfigurationWithDefault<string>(
|
||||
"/tmp/orchestration_downloads",
|
||||
"orchestration",
|
||||
"Default file download path"
|
||||
);
|
||||
string service_name = "test";
|
||||
string download_file_name = service_name + ".download";
|
||||
string download_file_path = dir_path + "/" + download_file_name;
|
||||
string checksum = "1234";
|
||||
Package::ChecksumTypes checksum_type = Package::ChecksumTypes::MD5;
|
||||
EXPECT_CALL(mock_orchestration_tools, copyFile(custom_URL + file_name, download_file_path))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, isNonEmptyFile(download_file_path)).WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(checksum_type, download_file_path))
|
||||
.WillOnce(Return(checksum));
|
||||
|
||||
i_downloader->downloadFileFromURL(url, checksum, checksum_type, service_name);
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, CustomURLBackBackslash)
|
||||
{
|
||||
string file_prefix = "file://";
|
||||
string file_name = "test_file.sh";
|
||||
string local_file_path = "/tmp/" + file_name;
|
||||
string url = file_prefix + local_file_path;
|
||||
string custom_URL = "/custom/";
|
||||
setConfiguration<string>(
|
||||
string(file_prefix + custom_URL),
|
||||
"orchestration",
|
||||
"Custom download url"
|
||||
);
|
||||
string dir_path = getConfigurationWithDefault<string>(
|
||||
"/tmp/orchestration_downloads",
|
||||
"orchestration",
|
||||
"Default file download path"
|
||||
);
|
||||
string service_name = "test";
|
||||
string download_file_name = service_name + ".download";
|
||||
string download_file_path = dir_path + "/" + download_file_name;
|
||||
string checksum = "1234";
|
||||
Package::ChecksumTypes checksum_type = Package::ChecksumTypes::MD5;
|
||||
EXPECT_CALL(mock_orchestration_tools, copyFile(custom_URL + file_name, download_file_path))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, isNonEmptyFile(download_file_path)).WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(checksum_type, download_file_path))
|
||||
.WillOnce(Return(checksum));
|
||||
i_downloader->downloadFileFromURL(url, checksum, checksum_type, service_name);
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, EmptyCustomURL)
|
||||
{
|
||||
string file_prefix = "file://";
|
||||
string file_name = "/test_file.sh";
|
||||
string local_file_path = "/tmp" + file_name;
|
||||
string url = file_prefix + local_file_path;
|
||||
string custom_URL = "";
|
||||
setConfiguration<string>(
|
||||
string(custom_URL),
|
||||
"orchestration",
|
||||
"Custom download url"
|
||||
);
|
||||
string dir_path = getConfigurationWithDefault<string>(
|
||||
"/tmp/orchestration_downloads",
|
||||
"orchestration",
|
||||
"Default file download path"
|
||||
);
|
||||
string service_name = "test";
|
||||
string download_file_name = service_name + ".download";
|
||||
string download_file_path = dir_path + "/" + download_file_name;
|
||||
string checksum = "1234";
|
||||
Package::ChecksumTypes checksum_type = Package::ChecksumTypes::MD5;
|
||||
EXPECT_THAT(
|
||||
i_downloader->downloadFileFromURL(url, checksum, checksum_type, service_name),
|
||||
IsError("Failed to parse custom URL. URL is empty")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, download_virtual_policy)
|
||||
{
|
||||
GetResourceFile resourse_file(GetResourceFile::ResourceFileType::VIRTUAL_POLICY);
|
||||
|
||||
resourse_file.addTenant("0000", "1", "checksum0000");
|
||||
resourse_file.addTenant("1111", "2", "checksum1111");
|
||||
|
||||
string tenant_0000_file =
|
||||
"{"
|
||||
"\"waap\":\"108-005\","
|
||||
"\"accessControl\":\"Internal error, check logs\","
|
||||
"\"idk\":\"ed5ac9a6-6924-4ebc-9ace-971896ca33c5\","
|
||||
"\"something\":\"Low\""
|
||||
"}";
|
||||
|
||||
string tenant_1111_file =
|
||||
"{"
|
||||
"\"messageId\":\"108-005\","
|
||||
"\"message\":\"Internal error, check logs\","
|
||||
"\"referenceId\":\"ed5ac9a6-6924-4ebc-9ace-971896ca33c5\","
|
||||
"\"severity\":\"Low\""
|
||||
"}";
|
||||
|
||||
string fog_response =
|
||||
"{\n"
|
||||
" \"tenants\": [\n"
|
||||
" {\n"
|
||||
" \"tenantId\": \"0000\",\n"
|
||||
" \"policy\": {\n"
|
||||
" \"waap\": \"108-005\",\n"
|
||||
" \"accessControl\": \"Internal error, check logs\",\n"
|
||||
" \"idk\": \"ed5ac9a6-6924-4ebc-9ace-971896ca33c5\",\n"
|
||||
" \"something\": \"Low\"\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"tenantId\": \"1111\",\n"
|
||||
" \"policy\": {\n"
|
||||
" \"messageId\": \"108-005\",\n"
|
||||
" \"message\": \"Internal error, check logs\",\n"
|
||||
" \"referenceId\": \"ed5ac9a6-6924-4ebc-9ace-971896ca33c5\",\n"
|
||||
" \"severity\": \"Low\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}";
|
||||
|
||||
EXPECT_CALL(mock_communication, downloadAttributeFile(resourse_file)).WillOnce(Return(fog_response));
|
||||
|
||||
EXPECT_CALL(mock_orchestration_tools, writeFile(tenant_0000_file, "/tmp/virtualPolicy_0000.download"))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(mock_orchestration_tools, writeFile(tenant_1111_file, "/tmp/virtualPolicy_1111.download"))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
map<string, string> expected_downloaded_files =
|
||||
{
|
||||
{ "0000", "/tmp/virtualPolicy_0000.download" },
|
||||
{ "1111", "/tmp/virtualPolicy_1111.download" }
|
||||
};
|
||||
|
||||
EXPECT_EQ(
|
||||
i_downloader->downloadVirtualFileFromFog(
|
||||
resourse_file,
|
||||
Package::ChecksumTypes::SHA256
|
||||
),
|
||||
expected_downloaded_files
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(DownloaderTest, download_virtual_settings)
|
||||
{
|
||||
GetResourceFile resourse_file(GetResourceFile::ResourceFileType::VIRTUAL_SETTINGS);
|
||||
|
||||
resourse_file.addTenant("4c721b40-85df-4364-be3d-303a10ee9789", "1", "checksum0000");
|
||||
|
||||
string tenant_0000_file =
|
||||
"{"
|
||||
"\"agentSettings\":["
|
||||
"{"
|
||||
"\"id\":\"f0bd081b-175a-2fb6-c6de-d05d62fdcadf\","
|
||||
"\"key\":\"\","
|
||||
"\"value\":\"\""
|
||||
"}"
|
||||
"],"
|
||||
"\"allowOnlyDefinedApplications\":false,"
|
||||
"\"anyFog\":true,"
|
||||
"\"reverseProxy\":{"
|
||||
"\"assets\":[]"
|
||||
"},"
|
||||
"\"upgradeMode\":\"automatic\""
|
||||
"}";
|
||||
|
||||
string fog_response =
|
||||
"{\n"
|
||||
" \"tenants\": [\n"
|
||||
" {\n"
|
||||
" \"tenantId\": \"4c721b40-85df-4364-be3d-303a10ee9789\",\n"
|
||||
" \"settings\": {\n"
|
||||
" \"agentSettings\": [\n"
|
||||
" {\n"
|
||||
" \"id\": \"f0bd081b-175a-2fb6-c6de-d05d62fdcadf\",\n"
|
||||
" \"key\": \"\",\n"
|
||||
" \"value\": \"\"\n"
|
||||
" }\n"
|
||||
" ],\n"
|
||||
" \"allowOnlyDefinedApplications\": false,\n"
|
||||
" \"anyFog\": true,\n"
|
||||
" \"reverseProxy\": {\n"
|
||||
" \"assets\": []\n"
|
||||
" },\n"
|
||||
" \"upgradeMode\": \"automatic\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}";
|
||||
|
||||
EXPECT_CALL(mock_communication, downloadAttributeFile(resourse_file)).WillOnce(Return(fog_response));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_orchestration_tools,
|
||||
writeFile(tenant_0000_file, "/tmp/virtualSettings_4c721b40-85df-4364-be3d-303a10ee9789.download")
|
||||
).WillOnce(Return(true));
|
||||
|
||||
map<string, string> expected_downloaded_files = {
|
||||
{ "4c721b40-85df-4364-be3d-303a10ee9789",
|
||||
"/tmp/virtualSettings_4c721b40-85df-4364-be3d-303a10ee9789.download"
|
||||
}
|
||||
};
|
||||
|
||||
EXPECT_EQ(
|
||||
i_downloader->downloadVirtualFileFromFog(
|
||||
resourse_file,
|
||||
Package::ChecksumTypes::SHA256
|
||||
),
|
||||
expected_downloaded_files
|
||||
);
|
||||
}
|
276
components/security_apps/orchestration/downloader/http_client.cc
Executable file
276
components/security_apps/orchestration/downloader/http_client.cc
Executable file
@@ -0,0 +1,276 @@
|
||||
// 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 "sasal.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;
|
||||
|
||||
SASAL_START // Orchestration - Communication
|
||||
|
||||
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 message = Singleton::Consume<I_Messaging>::by<HTTPClient>();
|
||||
auto load_env_proxy = message->loadProxy();
|
||||
if (!load_env_proxy.ok()) return load_env_proxy;
|
||||
|
||||
string token = "";
|
||||
if (auth_required) {
|
||||
auto message = Singleton::Consume<I_Messaging>::by<HTTPClient>();
|
||||
token = message->getAccessToken();
|
||||
}
|
||||
|
||||
if (url.isOverSSL()) {
|
||||
auto get_file_over_ssl_res = getFileSSL(url, out_file, token);
|
||||
if (!get_file_over_ssl_res.ok())
|
||||
{
|
||||
//CURL fallback
|
||||
dbgWarning(D_ORCHESTRATOR) << "Failed to get file over SSL. Trying via CURL (SSL).";
|
||||
return curlGetFileOverSSL(url, out_file, token);
|
||||
}
|
||||
return get_file_over_ssl_res;
|
||||
}
|
||||
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 message = Singleton::Consume<I_Messaging>::by<HTTPClient>();
|
||||
|
||||
HttpCurl http_curl_client(
|
||||
url,
|
||||
out_file,
|
||||
token,
|
||||
message->getProxyDomain(ProxyProtocol::HTTPS),
|
||||
message->getProxyPort(ProxyProtocol::HTTPS),
|
||||
message->getProxyCredentials(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 message = Singleton::Consume<I_Messaging>::by<HTTPClient>();
|
||||
ClientConnection client_connection(
|
||||
url,
|
||||
message->getProxyDomain(ProxyProtocol::HTTP),
|
||||
message->getProxyPort(ProxyProtocol::HTTP),
|
||||
message->getProxyCredentials(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
|
||||
|
||||
SASAL_END
|
39
components/security_apps/orchestration/downloader/http_client.h
Executable file
39
components/security_apps/orchestration/downloader/http_client.h
Executable file
@@ -0,0 +1,39 @@
|
||||
// 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_messaging.h"
|
||||
|
||||
// LCOV_EXCL_START Reason: Depends on real download server.
|
||||
class HTTPClient : public Singleton::Consume<I_Messaging>
|
||||
{
|
||||
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> getFileHttp(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);
|
||||
Maybe<void> curlGetFileOverHttp(const URLParser &url, std::ofstream &out_file, const std::string &_token);
|
||||
};
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
#endif // __HTTP_CLIENT_H__
|
619
components/security_apps/orchestration/downloader/https_client.cc
Executable file
619
components/security_apps/orchestration/downloader/https_client.cc
Executable file
@@ -0,0 +1,619 @@
|
||||
// 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 "debug.h"
|
||||
#include "i_agent_details.h"
|
||||
#include "i_encryptor.h"
|
||||
#include "downloader.h"
|
||||
#include "config.h"
|
||||
#include "sasal.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 <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>
|
||||
|
||||
using namespace boost::placeholders;
|
||||
using boost::asio::ip::tcp;
|
||||
using namespace std;
|
||||
|
||||
SASAL_START // Orchestration - Communication
|
||||
|
||||
USE_DEBUG_FLAG(D_COMMUNICATION);
|
||||
USE_DEBUG_FLAG(D_HTTP_REQUEST);
|
||||
USE_DEBUG_FLAG(D_ORCHESTRATOR);
|
||||
|
||||
// LCOV_EXCL_START Reason: Depends on real download server.
|
||||
class BadResponseFromServer : public exception
|
||||
{
|
||||
public:
|
||||
BadResponseFromServer() : message("Bad response returned from server") {}
|
||||
BadResponseFromServer(const string &msg) : message(msg) {}
|
||||
const char *
|
||||
what() const throw()
|
||||
{
|
||||
return message.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
string message;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
Maybe<void>
|
||||
handleConnection()
|
||||
{
|
||||
ostream request_stream(&request_);
|
||||
stringstream http_request;
|
||||
http_request << "GET " << url.getQuery() << " HTTP/1.1\r\n";
|
||||
string host = url.getBaseURL().unpack();
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
string
|
||||
HTTPClient::loadCAChainDir()
|
||||
{
|
||||
string ca_chain_dir;
|
||||
auto agent_details = Singleton::Consume<I_AgentDetails>::by<Downloader>();
|
||||
auto load_ca_chain_dir = agent_details->getOpenSSLDir();
|
||||
if (load_ca_chain_dir.ok()) {
|
||||
ca_chain_dir = load_ca_chain_dir.unpack();
|
||||
}
|
||||
return getConfigurationWithDefault<string>(ca_chain_dir, "message", "Certificate authority directory");
|
||||
}
|
||||
|
||||
Maybe<void>
|
||||
HTTPClient::getFileSSL(const URLParser &url, ofstream &out_file, const string &token)
|
||||
{
|
||||
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 message = Singleton::Consume<I_Messaging>::by<HTTPClient>();
|
||||
|
||||
Client client(
|
||||
out_file,
|
||||
io_service,
|
||||
ctx,
|
||||
url,
|
||||
message->getProxyDomain(ProxyProtocol::HTTPS),
|
||||
message->getProxyPort(ProxyProtocol::HTTPS),
|
||||
message->getProxyCredentials(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);
|
||||
}
|
||||
|
||||
return Maybe<void>();
|
||||
}
|
||||
|
||||
Maybe<void>
|
||||
HTTPClient::curlGetFileOverSSL(const URLParser &url, ofstream &out_file, const string &token)
|
||||
{
|
||||
try {
|
||||
string cert_file_path;
|
||||
if (!token.empty())
|
||||
{
|
||||
cert_file_path = getConfigurationWithDefault<string>(
|
||||
getFilesystemPathConfig() + "/certs/fog.pem",
|
||||
"message",
|
||||
"Certificate chain file path"
|
||||
);
|
||||
}
|
||||
|
||||
auto message = Singleton::Consume<I_Messaging>::by<HTTPClient>();
|
||||
|
||||
HttpsCurl ssl_curl_client(
|
||||
url,
|
||||
out_file,
|
||||
token,
|
||||
message->getProxyDomain(ProxyProtocol::HTTPS),
|
||||
message->getProxyPort(ProxyProtocol::HTTPS),
|
||||
message->getProxyCredentials(ProxyProtocol::HTTPS),
|
||||
cert_file_path);
|
||||
|
||||
ssl_curl_client.setCurlOpts();
|
||||
bool connection_ok = ssl_curl_client.connect();
|
||||
if (!connection_ok)
|
||||
{
|
||||
stringstream url_s;
|
||||
url_s << url;
|
||||
string err_msg = string("Failed to get file over HTTPS. URL: ") + url_s.str();
|
||||
return genError(err_msg);
|
||||
}
|
||||
|
||||
} 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);
|
||||
}
|
||||
|
||||
return Maybe<void>();
|
||||
}
|
||||
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
SASAL_END
|
Reference in New Issue
Block a user