mirror of
https://github.com/openappsec/openappsec.git
synced 2025-06-28 16:41:02 +03:00
504 lines
18 KiB
C++
Executable File
504 lines
18 KiB
C++
Executable File
// 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 "https_client.h"
|
|
#include "debug.h"
|
|
#include "config.h"
|
|
#include "rest.h"
|
|
#include "cereal/external/rapidjson/document.h"
|
|
|
|
#include "customized_cereal_map.h"
|
|
#include "cereal/archives/json.hpp"
|
|
#include "cereal/types/vector.hpp"
|
|
#include "cereal/types/string.hpp"
|
|
|
|
#include <fstream>
|
|
|
|
using namespace std;
|
|
using namespace rapidjson;
|
|
|
|
USE_DEBUG_FLAG(D_ORCHESTRATOR);
|
|
|
|
// LCOV_EXCL_START Reason: WA for NSaaS upgrade
|
|
class TenantProfileMap
|
|
{
|
|
public:
|
|
void
|
|
load(const string &raw_value)
|
|
{
|
|
vector<string> tenants_and_profiles;
|
|
{
|
|
stringstream string_stream(raw_value);
|
|
cereal::JSONInputArchive archive(string_stream);
|
|
cereal::load(archive, tenants_and_profiles);
|
|
}
|
|
for (const auto &tenant_profile_pair : tenants_and_profiles) {
|
|
value.push_back(tenant_profile_pair);
|
|
}
|
|
}
|
|
|
|
const vector<string> & getValue() const { return value; }
|
|
private:
|
|
vector<string> value;
|
|
};
|
|
|
|
// LCOV_EXCL_STOP
|
|
|
|
class Downloader::Impl : Singleton::Provide<I_Downloader>::From<Downloader>
|
|
{
|
|
public:
|
|
void init();
|
|
|
|
Maybe<string> downloadFile(
|
|
const string &checksum,
|
|
Package::ChecksumTypes checksum_type,
|
|
const GetResourceFile &resourse_file
|
|
) const override;
|
|
|
|
Maybe<map<pair<string, 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;
|
|
|
|
Maybe<string> checkIfFileExists(const Package &package) const override;
|
|
void removeDownloadFile(const string &file_name) const override;
|
|
void createTenantProfileMap();
|
|
string getProfileFromMap(const string &tenant_id) const override;
|
|
|
|
private:
|
|
Maybe<string> downloadAttributeFile(
|
|
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;
|
|
map<string, string> tenant_profile_map;
|
|
};
|
|
|
|
void
|
|
Downloader::Impl::init()
|
|
{
|
|
dir_path = getConfigurationWithDefault<string>(
|
|
"/tmp/orchestration_downloads",
|
|
"orchestration",
|
|
"Default file download path"
|
|
);
|
|
|
|
auto maybe_vs_id = Singleton::Consume<I_Environment>::by<Downloader>()->get<string>("VS ID");
|
|
if (maybe_vs_id.ok()) {
|
|
dir_path = dir_path + "/vs" + maybe_vs_id.unpack();
|
|
}
|
|
|
|
Singleton::Consume<I_OrchestrationTools>::by<Downloader>()->createDirectory(dir_path);
|
|
}
|
|
|
|
Maybe<string>
|
|
Downloader::Impl::downloadFile(
|
|
const string &checksum,
|
|
Package::ChecksumTypes checksum_type,
|
|
const GetResourceFile &resourse_file) const
|
|
{
|
|
auto file_path = downloadAttributeFile(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;
|
|
}
|
|
|
|
void
|
|
Downloader::Impl::createTenantProfileMap()
|
|
{
|
|
dbgFlow(D_ORCHESTRATOR) << "Creating a tenant-profile map from the agent settings";
|
|
tenant_profile_map.clear();
|
|
auto maybe_tenant_profile_map = getProfileAgentSetting<TenantProfileMap>("TenantProfileMap");
|
|
if (maybe_tenant_profile_map.ok()) {
|
|
dbgTrace(D_ORCHESTRATOR) << "Managed to read the TenantProfileMap agent settings";
|
|
TenantProfileMap tpm = maybe_tenant_profile_map.unpack();
|
|
for (const string &str : tpm.getValue()) {
|
|
string delimiter = ":";
|
|
string tenant = str.substr(0, str.find(delimiter));
|
|
string profile = str.substr(str.find(delimiter) + 1);
|
|
dbgTrace(D_ORCHESTRATOR)
|
|
<< "Loading into the map. Tenant: "
|
|
<< tenant
|
|
<< " Profile: "
|
|
<< profile;
|
|
tenant_profile_map[tenant] = profile;
|
|
}
|
|
} else {
|
|
dbgTrace(D_ORCHESTRATOR) << "Couldn't load the TenantProfileMap agent settings";
|
|
}
|
|
}
|
|
|
|
// LCOV_EXCL_START Reason: NSaaS old profiles support
|
|
string
|
|
Downloader::Impl::getProfileFromMap(const string &tenant_id) const
|
|
{
|
|
if (tenant_profile_map.find(tenant_id) == tenant_profile_map.end()) {
|
|
return "";
|
|
}
|
|
return tenant_profile_map.at(tenant_id);
|
|
}
|
|
// LCOV_EXCL_STOP
|
|
|
|
Maybe<map<pair<string, string>, string>>
|
|
Downloader::Impl::downloadVirtualFileFromFog(
|
|
const GetResourceFile &resourse_file,
|
|
Package::ChecksumTypes) const
|
|
{
|
|
static const string tenand_id_key = "tenantId";
|
|
static const string profile_id_key = "profileId";
|
|
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<pair<string, string>, string> res;
|
|
|
|
string general_file_path = dir_path + "/" + resourse_file.getFileName() + "_general.download";
|
|
I_UpdateCommunication *update_communication = Singleton::Consume<I_UpdateCommunication>::by<Downloader>();
|
|
auto downloaded_data = update_communication->downloadAttributeFile(resourse_file, general_file_path);
|
|
if (!downloaded_data.ok()) return downloaded_data.passErr();
|
|
|
|
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
|
Maybe<string> file_content = orchestration_tools->readFile(general_file_path);
|
|
if (!file_content.ok()) return file_content.passErr();
|
|
|
|
Document document;
|
|
document.Parse(file_content.unpack().c_str());
|
|
if (document.HasParseError()) {
|
|
dbgWarning(D_ORCHESTRATOR) << "JSON file is not valid";
|
|
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()) {
|
|
auto profile_id_obj = itr->FindMember(profile_id_key.c_str());
|
|
string profile_id;
|
|
if (profile_id_obj == itr->MemberEnd()) {
|
|
if (tenant_profile_map.count(tenant_id)) {
|
|
dbgWarning(D_ORCHESTRATOR)
|
|
<< "Forcing profile ID to be "
|
|
<< getProfileFromMap(tenant_id);
|
|
profile_id = getProfileFromMap(tenant_id);
|
|
} else {
|
|
dbgWarning(D_ORCHESTRATOR) << "Couldn't force profile ID";
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (profile_id.empty()) profile_id = profile_id_obj->value.GetString();
|
|
dbgTrace(D_ORCHESTRATOR) << "Found a profile ID " << profile_id;
|
|
|
|
string file_path =
|
|
dir_path + "/" + resourse_file.getFileName() + "_" +
|
|
tenant_id + "_profile_" + profile_id + ".download";
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
artifact_data->value.Accept(writer);
|
|
|
|
if (orchestration_tools->writeFile(buffer.GetString(), file_path)) {
|
|
res.insert({{tenant_id, profile_id}, file_path});
|
|
}
|
|
|
|
orchestration_tools->fillKeyInJson(file_path, "profileID", profile_id);
|
|
orchestration_tools->fillKeyInJson(file_path, "tenantID", tenant_id);
|
|
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::checkIfFileExists(const Package &package) const
|
|
{
|
|
string file_name = package.getName() + ".download";
|
|
Maybe<string> maybe_path = dir_path + "/" + file_name;
|
|
|
|
return validateChecksum(package.getChecksum(), package.getChecksumType(), maybe_path);
|
|
}
|
|
|
|
void
|
|
Downloader::Impl::removeDownloadFile(const string &file_name) const
|
|
{
|
|
string file_path = dir_path + "/" + file_name + ".download";
|
|
dbgInfo(D_ORCHESTRATOR) << "Removing download file " << file_path;
|
|
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<Downloader>();
|
|
orchestration_tools->removeFile(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::downloadAttributeFile(const GetResourceFile &resourse_file, const string &file_name) const
|
|
{
|
|
string file_path = dir_path + "/" + file_name;
|
|
|
|
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);
|
|
if (!downloaded_file.ok()) return genError(downloaded_file.getErr());
|
|
|
|
dbgInfo(D_ORCHESTRATOR) << "Download completed. File: " << resourse_file.getFileName();
|
|
return file_path;
|
|
}
|
|
|
|
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
|
|
{
|
|
dbgInfo(D_ORCHESTRATOR) << "Downloading file. URL: " << url;
|
|
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;
|
|
}
|
|
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");
|
|
registerConfigLoadCb([this]() { pimpl->createTenantProfileMap(); });
|
|
}
|