2024-04-21 13:57:46 +00:00

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(); });
}