First release of open-appsec source code

This commit is contained in:
roybarda
2022-10-26 19:33:19 +03:00
parent 3883109caf
commit a883352f79
1353 changed files with 276290 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
add_subdirectory(orchestration)
add_subdirectory(waap)

View File

@@ -0,0 +1,17 @@
add_definitions(-DUSERSPACE)
include_directories(include)
add_library(orchestration orchestration_comp.cc hybrid_mode_telemetry.cc)
add_subdirectory(orchestration_tools)
add_subdirectory(modules)
add_subdirectory(downloader)
add_subdirectory(service_controller)
add_subdirectory(package_handler)
add_subdirectory(manifest_controller)
add_subdirectory(update_communication)
add_subdirectory(details_resolver)
add_subdirectory(health_check)
add_subdirectory(k8s_policy_gen)
add_subdirectory(orchestration_ut)

View File

@@ -0,0 +1,3 @@
include_directories(details_resolver_handlers)
add_library(details_resolver details_resolver.cc details_resolving_handler.cc)

View File

@@ -0,0 +1,288 @@
// 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 "details_resolver.h"
#include <sstream>
#include <string>
#include <vector>
#include "details_resolving_handler.h"
#include "i_orchestration_tools.h"
#include "maybe_res.h"
#include "version.h"
#include "config.h"
using namespace std;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
class DetailsResolver::Impl
:
Singleton::Provide<I_DetailsResolver>::From<DetailsResolver>
{
public:
Maybe<string> getHostname() override;
Maybe<string> getPlatform() override;
Maybe<string> getArch() override;
map<string, string> getResolvedDetails() override;
string getAgentVersion() override;
bool isKernelVersion3OrHigher() override;
bool isGwNotVsx() override;
bool isVersionEqualOrAboveR8110() override;
bool isReverseProxy() override;
Maybe<tuple<string, string, string>> parseNginxMetadata() override;
#if defined(gaia) || defined(smb)
bool compareCheckpointVersion(int cp_version, std::function<bool(int, int)> compare_operator) const override;
#endif // gaia || smb
private:
#if defined(gaia) || defined(smb)
int getCheckpointVersion() const;
#endif // gaia || smb
DetailsResolvingHanlder handler;
};
map<string, string>
DetailsResolver::Impl::getResolvedDetails()
{
return handler.getResolvedDetails();
}
Maybe<string>
DetailsResolver::Impl::getHostname()
{
#if defined(arm32_musl) || defined(openwrt)
auto host_name = DetailsResolvingHanlder::getCommandOutput("uname -a | awk '{print $(2)}'");
#else // not arm32_musl || openwrt
auto host_name = DetailsResolvingHanlder::getCommandOutput("hostname");
#endif // defined(arm32_musl) || defined(openwrt)
if (!host_name.ok()) return genError("Failed to load host name, Error: " + host_name.getErr());
return host_name;
}
Maybe<string>
DetailsResolver::Impl::getPlatform()
{
#if defined(gaia)
return string("gaia");
#elif defined(arm32_rpi)
return string("glibc");
#elif defined(arm32_musl)
return string("musl");
#elif defined(smb_mrv_v1)
return string("smb_mrv_v1");
#elif defined(smb_sve_v2)
return string("smb_sve_v2");
#elif defined(smb_thx_v3)
return string("smb_thx_v3");
#elif defined(openwrt)
return string("uclibc");
#elif defined(arm64_linaro)
return string("arm64_linaro");
#elif defined(alpine)
return string("alpine");
#elif defined(arm64_trustbox)
return string("arm64_trustbox");
#elif defined(linux)
return string("linux");
#else
return genError("Failed to load platform details");
#endif
}
Maybe<string>
DetailsResolver::Impl::getArch()
{
#if defined(arm32_rpi) || defined(arm32_musl) || defined(openwrt)
auto architecture = DetailsResolvingHanlder::getCommandOutput("uname -a | awk '{print $(NF -1) }'");
#else // not arm32_rpi || arm32_musl || openwrt
auto architecture = DetailsResolvingHanlder::getCommandOutput("arch");
#endif // defined(arm32_rpi) || defined(arm32_musl) || defined(openwrt)
if (!architecture.ok()) return genError("Failed to load platform architecture, Error: " + architecture.getErr());
return architecture;
}
string
DetailsResolver::Impl::getAgentVersion()
{
return Version::getFullVersion();
}
bool
DetailsResolver::Impl::isReverseProxy()
{
#if defined(gaia) || defined(smb)
auto is_reverse_proxy = DetailsResolvingHanlder::getCommandOutput("cpprod_util CPPROD_IsConfigured CPwaap");
if (is_reverse_proxy.ok()) {
return is_reverse_proxy.unpack().front() == '1';
}
#endif
return false;
}
bool
DetailsResolver::Impl::isKernelVersion3OrHigher()
{
static const string cmd =
"clish -c 'show version os kernel' | awk '{print $4}' "
"| cut -d '.' -f 1 | awk -F: '{ if ( $1 >= 3 ) {print 1} else {print 0}}'";
auto is_gogo = DetailsResolvingHanlder::getCommandOutput(cmd);
if (is_gogo.ok()) {
return is_gogo.unpack().front() == '1';
}
return false;
}
bool
DetailsResolver::Impl::isGwNotVsx()
{
static const string is_gw_cmd = "cpprod_util FwIsFirewallModule";
static const string is_vsx_cmd = "cpprod_util FWisVSX";
auto is_gw = DetailsResolvingHanlder::getCommandOutput(is_gw_cmd);
auto is_vsx = DetailsResolvingHanlder::getCommandOutput(is_vsx_cmd);
if (is_gw.ok() && is_vsx.ok()) {
return is_gw.unpack().front() == '1' && is_vsx.unpack().front() == '0';
}
return false;
}
#if defined(gaia) || defined(smb)
bool
DetailsResolver::Impl::compareCheckpointVersion(int cp_version, std::function<bool(int, int)> compare_operator) const
{
int curr_version = getCheckpointVersion();
return compare_operator(curr_version, cp_version);
}
int
DetailsResolver::Impl::getCheckpointVersion() const
{
#ifdef gaia
static const string cmd =
"echo $CPDIR | awk -F'-' '{print $NF}' | cut -c 2- |"
" awk -F'.' '{ if( NF == 1 ) {print $1\"00\"} else {print $1$2} }'";
#else // smb
static const string cmd = "sqlcmd 'select major,minor from cpver' |"
"awk '{if ($1 == \"major\") v += (substr($3,2) * 100);"
" if ($1 == \"minor\") v += $3; } END { print v}'";
#endif // gaia
auto version_out = DetailsResolvingHanlder::getCommandOutput(cmd);
int cp_version = 0;
if (version_out.ok()) {
stringstream version_stream(version_out.unpack());
version_stream >> cp_version;
}
return cp_version;
}
#endif // gaia || smb
bool
DetailsResolver::Impl::isVersionEqualOrAboveR8110()
{
#if defined(gaia) || defined(smb)
return compareCheckpointVersion(8110, std::greater_equal<int>());
#endif
return false;
}
Maybe<tuple<string, string, string>>
DetailsResolver::Impl::parseNginxMetadata()
{
auto output_path = getConfigurationWithDefault<string>(
"/tmp/nginx_meta_data.txt",
"orchestration",
"Nginx metadata temp file"
);
const string srcipt_exe_cmd =
getFilesystemPathConfig() +
"/scripts/cp-nano-makefile-generator.sh -f -o " +
output_path;
dbgTrace(D_ORCHESTRATOR) << "Details resolver, srcipt exe cmd: " << srcipt_exe_cmd;
auto is_nginx_exist = DetailsResolvingHanlder::getCommandOutput("which nginx");
if (!is_nginx_exist.ok() || is_nginx_exist.unpack().size() == 0) {
return genError("Nginx isn't installed");
}
auto script_output = DetailsResolvingHanlder::getCommandOutput(srcipt_exe_cmd);
if (!script_output.ok()) {
return genError("Failed to generate nginx metadata, Error: " + script_output.getErr());
}
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<DetailsResolver>();
if (!orchestration_tools->doesFileExist(output_path)) {
return genError("Failed to access nginx metadata file.");
}
vector<string> lines;
try {
ifstream input_stream(output_path);
if (!input_stream) {
return genError("Cannot open the file with nginx metadata, File: " + output_path);
}
string line;
while (getline(input_stream, line)) {
lines.push_back(line);
}
input_stream.close();
orchestration_tools->removeFile(output_path);
} catch (const ifstream::failure &exception) {
dbgWarning(D_ORCHESTRATOR)
<< "Cannot read the file with required nginx metadata."
<< " File: " << output_path
<< " Error: " << exception.what();
}
if (lines.size() == 0) return genError("Failed to read nginx metadata file");
string nginx_version;
string config_opt;
string cc_opt;
for(string &line : lines) {
if (line.size() == 0) continue;
if (line.find("RELEASE_VERSION") != string::npos) continue;
if (line.find("--with-cc=") != string::npos) continue;
if (line.find("NGINX_VERSION") != string::npos) {
auto eq_index = line.find("=");
nginx_version = "nginx-" + line.substr(eq_index + 1);
continue;
}
if (line.find("EXTRA_CC_OPT") != string::npos) {
auto eq_index = line.find("=");
cc_opt = line.substr(eq_index + 1);
continue;
}
if (line.find("CONFIGURE_OPT") != string::npos) continue;
if (line.back() == '\\') line.pop_back();
config_opt += line;
}
return make_tuple(config_opt, cc_opt, nginx_version);
}
DetailsResolver::DetailsResolver() : Component("DetailsResolver"), pimpl(make_unique<Impl>()) {}
DetailsResolver::~DetailsResolver() {}
void
DetailsResolver::preload()
{
registerExpectedConfiguration<uint32_t>("orchestration", "Details resolver time out");
}

View File

@@ -0,0 +1,183 @@
// 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 __CHECKPOINT_PRODUCT_HANDLERS_H__
#define __CHECKPOINT_PRODUCT_HANDLERS_H__
#include <algorithm>
#include <boost/regex.hpp>
#if defined(gaia) || defined(smb)
Maybe<string>
checkHasSDWan(const string &command_output)
{
if (command_output.front() == '1') return string("true");
return genError("Current host does not have SDWAN capability");
}
Maybe<string>
getMgmtObjType(const string &command_output)
{
if (!command_output.empty()) {
if (command_output[0] == '1') return string("management");
if (command_output[0] == '0') return string("gateway");
}
return genError("Object type was not found");
}
Maybe<string>
chopHeadAndTail(const string &str, const string &prefix, const string &suffix)
{
if (str.size() < prefix.size() + suffix.size()) return genError("String too short");
if (str.compare(0, prefix.size(), prefix)) return genError("Prefix mismatch");
if (str.compare(str.size() - suffix.size(), suffix.size(), suffix)) return genError("Suffix mismatch");
return str.substr(prefix.size(), str.size() - prefix.size() - suffix.size());
}
Maybe<string>
getMgmtObjAttr(shared_ptr<istream> file_stream, const string &attr)
{
string line;
while (getline(*file_stream, line)) {
size_t attr_pos = line.find(attr);
if (attr_pos == string::npos) continue;
line = line.substr(attr_pos + attr.size());
return chopHeadAndTail(line, "(", ")");
}
return genError("Object attribute was not found. Attr: " + attr);
}
Maybe<string>
getMgmtObjUid(shared_ptr<istream> file_stream)
{
return getMgmtObjAttr(file_stream, "uuid ");
}
Maybe<string>
getMgmtObjName(shared_ptr<istream> file_stream)
{
return getMgmtObjAttr(file_stream, "name ");
}
Maybe<string>
getMgmtParentObjAttr(shared_ptr<istream> file_stream, const string &parent_obj, const string &attr)
{
string line;
bool found_parent_obj = false;
while (getline(*file_stream, line)) {
size_t parent_obj_pos = line.find(parent_obj);
if (parent_obj_pos != string::npos) found_parent_obj = true;
if (!found_parent_obj) continue;
size_t attr_pos = line.find(attr);
if (attr_pos == string::npos) continue;
line = line.substr(attr_pos + attr.size());
return line;
}
return genError("Parent object attribute was not found. Attr: " + attr);
}
Maybe<string>
getMgmtParentObjUid(shared_ptr<istream> file_stream)
{
auto maybe_unparsed_uid = getMgmtParentObjAttr(file_stream, "cluster_object", "Uid ");
if (!maybe_unparsed_uid.ok()) {
return maybe_unparsed_uid;
}
const string &unparsed_uid = maybe_unparsed_uid.unpack();
auto maybe_uid = chopHeadAndTail(unparsed_uid, "(\"{", "}\")");
if (!maybe_uid.ok()) {
return maybe_uid;
}
string uid = maybe_uid.unpack();
transform(uid.begin(), uid.end(), uid.begin(), ::tolower);
return uid;
}
Maybe<string>
getMgmtParentObjName(shared_ptr<istream> file_stream)
{
auto maybe_unparsed_name = getMgmtParentObjAttr(file_stream, "cluster_object", "Name ");
if (!maybe_unparsed_name.ok()) {
return maybe_unparsed_name;
}
const string &unparsed_name = maybe_unparsed_name.unpack();
return chopHeadAndTail(unparsed_name, "(", ")");
}
#endif // gaia || smb
Maybe<string>
getOsRelease(shared_ptr<istream> file_stream)
{
string line;
while (getline(*file_stream, line)) {
if (line.find("Check Point") != string::npos) return line;
static const string prety_name_attr = "PRETTY_NAME=";
size_t pretty_name_idx = line.find(prety_name_attr);
if (pretty_name_idx == string::npos) continue;
line = line.substr(pretty_name_idx + prety_name_attr.size());
if (line.front() == '"') line.erase(0, 1);
if (line.back() == '"') line.pop_back();
return line;
}
return genError("Os release was not found");
}
#if defined(alpine)
string &
ltrim(string &s)
{
auto it = find_if(
s.begin(),
s.end(),
[](char c) { return !isspace<char>(c, locale::classic()); }
);
s.erase(s.begin(), it);
return s;
}
string &
rtrim(string &s)
{
auto it = find_if(
s.rbegin(),
s.rend(),
[](char c) { return !isspace<char>(c, locale::classic()); }
);
s.erase(it.base(), s.end());
return s;
}
string &
trim(string &s)
{
return ltrim(rtrim(s));
}
Maybe<string>
getCPAlpineTag(shared_ptr<istream> file_stream)
{
string line;
while (getline(*file_stream, line)) {
if (trim(line) != "") return line;
}
return genError("Alpine tag was not found");
}
#endif // alpine
#endif // __CHECKPOINT_PRODUCT_HANDLERS_H__

View File

@@ -0,0 +1,72 @@
// 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 __DETAILS_RESOLVER_HANDLER_CC__
#error details_resolver_handlers/details_resolver_impl.h should not be included directly.
#endif // __DETAILS_RESOLVER_HANDLER_CC__
// use SHELL_CMD_HANDLER(key as string, shell command as string, ptr to Maybe<string> handler(const string&))
// to return a string value for an attribute key based on a logic executed in a handler that receives
// shell command execution output as its input
#ifdef SHELL_CMD_HANDLER
#if defined(gaia) || defined(smb)
SHELL_CMD_HANDLER("cpProductIntegrationMgmtObjectType", "cpprod_util CPPROD_IsMgmtMachine", getMgmtObjType)
SHELL_CMD_HANDLER("hasSDWan", "[ -f $FWDIR/bin/sdwan_steering ] && echo '1' || echo '0'", checkHasSDWan)
#endif //gaia
#endif // SHELL_CMD_HANDLER
// use SHELL_CMD_OUTPUT(key as string, shell command as string) to return a shell command output as the value
// for a given key
#ifdef SHELL_CMD_OUTPUT
SHELL_CMD_OUTPUT("kernel_version", "uname -r")
SHELL_CMD_OUTPUT("helloWorld", "cat /tmp/agentHelloWorld 2>/dev/null")
#endif // SHELL_CMD_OUTPUT
// use FILE_CONTENT_HANDLER(key as string, path to file as string, ptr to Maybe<string> handler(ifstream&))
// to return a string value for an attribute key based on a logic executed in a handler that receives file as input
#ifdef FILE_CONTENT_HANDLER
#if defined(alpine)
FILE_CONTENT_HANDLER("alpine_tag", "/usr/share/build/cp-alpine-tag", getCPAlpineTag)
#endif // alpine
#if defined(gaia) || defined(smb)
FILE_CONTENT_HANDLER("os_release", "/etc/cp-release", getOsRelease)
FILE_CONTENT_HANDLER(
"cpProductIntegrationMgmtObjectUid",
(getenv("FWDIR") ? string(getenv("FWDIR")) : "") + "/database/myown.C",
getMgmtObjUid
)
FILE_CONTENT_HANDLER(
"cpProductIntegrationMgmtObjectName",
(getenv("FWDIR") ? string(getenv("FWDIR")) : "") + "/database/myown.C",
getMgmtObjName
)
FILE_CONTENT_HANDLER(
"cpProductIntegrationMgmtParentObjectUid",
(getenv("FWDIR") ? string(getenv("FWDIR")) : "") + "/database/myself_objects.C",
getMgmtParentObjUid
)
FILE_CONTENT_HANDLER(
"cpProductIntegrationMgmtParentObjectName",
(getenv("FWDIR") ? string(getenv("FWDIR")) : "") + "/database/myself_objects.C",
getMgmtParentObjName
)
#else // !(gaia || smb)
FILE_CONTENT_HANDLER("os_release", "/etc/os-release", getOsRelease)
#endif // gaia || smb
#endif // FILE_CONTENT_HANDLER

View File

@@ -0,0 +1,128 @@
// 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 "details_resolving_handler.h"
#include <string>
#include <functional>
#include <map>
#include <fstream>
#include <iostream>
#include "maybe_res.h"
#include "enum_array.h"
#include "i_shell_cmd.h"
#include "config.h"
using namespace std;
USE_DEBUG_FLAG(D_AGENT_DETAILS);
using ShellCommandHandler = function<Maybe<string>(const string &raw_otput)>;
using FileContentHandler = function<Maybe<string>(shared_ptr<istream> file_otput)>;
#define __DETAILS_RESOLVER_HANDLER_CC__
#include "checkpoint_product_handlers.h"
class DetailsResolvingHanlder::Impl
{
public:
map<string, string> getResolvedDetails() const;
static Maybe<string> getCommandOutput(const string &cmd);
private:
#define SHELL_CMD_OUTPUT(ATTRIBUTE, COMMAND) SHELL_CMD_HANDLER(ATTRIBUTE, COMMAND, [](const string &s) { return s; })
#define SHELL_CMD_HANDLER(ATTRIBUTE, COMMAND, HANDLER) {ATTRIBUTE, {COMMAND, ShellCommandHandler(HANDLER)}},
map<string, pair<string, ShellCommandHandler>> shell_command_handlers = {
#include "details_resolver_impl.h"
};
#undef SHELL_CMD_OUTPUT
#undef SHELL_CMD_HANDLER
#define FILE_CONTENT_HANDLER(ATTRIBUTE, FILE, HANDLER) {ATTRIBUTE, {FILE, FileContentHandler(HANDLER)}},
map<string, pair<string, FileContentHandler>> file_content_handlers = {
#include "details_resolver_impl.h"
};
#undef FILE_CONTENT_HANDLER
};
map<string, string>
DetailsResolvingHanlder::Impl::getResolvedDetails() const
{
map<string, string> resolved_details;
for (auto shell_handler : shell_command_handlers) {
const string &attr = shell_handler.first;
const string &command = shell_handler.second.first;
ShellCommandHandler handler = shell_handler.second.second;
Maybe<string> shell_command_output = getCommandOutput(command);
if (!shell_command_output.ok()) continue;
Maybe<string> handler_ret = handler(*shell_command_output);
if (handler_ret.ok()) resolved_details[attr] = *handler_ret;
}
for (auto file_handler : file_content_handlers) {
const string &attr = file_handler.first;
const string &path = file_handler.second.first;
FileContentHandler handler = file_handler.second.second;
shared_ptr<ifstream> in_file = make_shared<ifstream>(path);
if (!in_file->is_open()) {
dbgWarning(D_AGENT_DETAILS) << "Could not open file for processing. Path: " << path;
continue;
}
dbgDebug(D_AGENT_DETAILS) << "Successfully opened file for processing. Path: " << path;
if (in_file->peek() != ifstream::traits_type::eof()) {
Maybe<string> handler_ret = handler(in_file);
if (handler_ret.ok()) resolved_details[attr] = *handler_ret;
}
in_file->close();
}
I_AgentDetailsReporter *reporter = Singleton::Consume<I_AgentDetailsReporter>::by<DetailsResolvingHanlder>();
reporter->addAttr(resolved_details);
return resolved_details;
}
Maybe<string>
DetailsResolvingHanlder::Impl::getCommandOutput(const string &cmd)
{
I_ShellCmd *shell = Singleton::Consume<I_ShellCmd>::by<DetailsResolvingHanlder>();
uint32_t timeout = getConfigurationWithDefault<uint32_t>(5000, "orchestration", "Details resolver time out");
auto result = shell->getExecOutput(cmd, timeout);
if (!result.ok()) return result;
auto unpacked_result = result.unpack();
if (unpacked_result.back() == '\n') unpacked_result.pop_back();
return unpacked_result;
}
DetailsResolvingHanlder::DetailsResolvingHanlder() : pimpl(make_unique<Impl>()) {}
DetailsResolvingHanlder::~DetailsResolvingHanlder() {}
map<string, string>
DetailsResolvingHanlder::getResolvedDetails() const
{
return pimpl->getResolvedDetails();
}
Maybe<string>
DetailsResolvingHanlder::getCommandOutput(const string &cmd)
{
return DetailsResolvingHanlder::Impl::getCommandOutput(cmd);
}

View File

@@ -0,0 +1,41 @@
// 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 __DETAILS_RESOLVING_HANDLER_H__
#define __DETAILS_RESOLVING_HANDLER_H__
#include <string>
#include <map>
#include "i_shell_cmd.h"
#include "i_agent_details_reporter.h"
class DetailsResolvingHanlder
:
Singleton::Consume<I_ShellCmd>,
Singleton::Consume<I_AgentDetailsReporter>
{
public:
DetailsResolvingHanlder();
~DetailsResolvingHanlder();
std::map<std::string, std::string> getResolvedDetails() const;
static Maybe<std::string> getCommandOutput(const std::string &cmd);
private:
class Impl;
std::unique_ptr<Impl> pimpl;
};
#endif // __DETAILS_RESOLVING_HANDLER_H__

View 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)

View 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

View 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

View 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

View File

@@ -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"
)

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

View 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

View 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__

View 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

View File

@@ -0,0 +1,3 @@
add_library(health_check health_check.cc)
add_subdirectory(health_check_ut)

View File

@@ -0,0 +1,340 @@
// 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 "health_checker.h"
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <unordered_map>
#include "config.h"
#include "log_generator.h"
#include "health_check_manager.h"
using namespace std;
using namespace ReportIS;
USE_DEBUG_FLAG(D_HEALTH_CHECK);
class HealthChecker::Impl
{
public:
void
init()
{
i_mainloop = Singleton::Consume<I_MainLoop>::by<HealthChecker>();
i_socket = Singleton::Consume<I_Socket>::by<HealthChecker>();
initConfig();
initServerSocket();
registerConfigLoadCb(
[&]()
{
initConfig();
initServerSocket();
}
);
}
void
initServerSocket()
{
if (!enable) {
return;
}
if (!checkInternalHealthCheckStatus()) {
reportError("Internal health check failed. Wait for restart.");
return;
}
if (port == 0) {
string error_msg =
"Cannot initialize health check component, listening port was not provided. "
"Please provide valid port (>0).";
reportError(error_msg);
return;
}
if (server_sock == -1) {
i_mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::System,
[this] () { HandleProbeStartup(); },
"Health check probe listener startup",
false
);
}
}
void
fini()
{
closeConnection();
}
private:
bool
checkInternalHealthCheckStatus()
{
dbgTrace(D_HEALTH_CHECK) << "Start agent general health check.";
HealthCheckStatus status =
Singleton::Consume<I_Health_Check_Manager>::by<HealthChecker>()->getAggregatedStatus();
dbgTrace(D_HEALTH_CHECK)
<< "Finished agent general health check. Received aggregated status: "
<< HealthCheckStatusReply::convertHealthCheckStatusToStr(status);
return status != HealthCheckStatus::UNHEALTHY;
}
void
reportError(const string &error_msg)
{
dbgWarning(D_HEALTH_CHECK) << error_msg;
LogGen(
error_msg,
Audience::SECURITY,
Severity::CRITICAL,
Priority::URGENT,
Tags::ORCHESTRATOR
);
}
void
closeConnection()
{
dbgDebug(D_HEALTH_CHECK) << "Closing connection";
if (server_sock > 0) {
i_socket->closeSocket(server_sock);
server_sock = -1;
dbgDebug(D_HEALTH_CHECK) << "Server socket closed";
}
if (routine_id > 0 && i_mainloop->doesRoutineExist(routine_id)) {
i_mainloop->stop(routine_id);
routine_id = 0;
}
for (auto socket_routine : client_sockets_routines) {
auto routine = socket_routine.first;
if (routine > 0 && i_mainloop->doesRoutineExist(routine)) {
i_mainloop->stop(routine);
}
auto socket = socket_routine.second;
if (socket > 0) {
i_socket->closeSocket(socket);
}
}
client_sockets_routines.clear();
}
void
initCloudVendorConfig()
{
static const map<string, pair<string, int>> ip_port_defaults_map = {
{"Azure", make_pair("168.63.129.16", 8117)},
{"Aws", make_pair("", 8117)}
};
auto cloud_vendor_maybe = getSetting<string>("reverseProxy", "cloudVendorName");
if (cloud_vendor_maybe.ok()) {
const string cloud_vendor = cloud_vendor_maybe.unpack();
auto value = ip_port_defaults_map.find(cloud_vendor);
if (value != ip_port_defaults_map.end()) {
const pair<string, uint> &ip_port_pair = value->second;
ip_address = ip_port_pair.first;
port = ip_port_pair.second;
enable = true;
}
}
ip_address = getProfileAgentSettingWithDefault<string>(
ip_address,
"agent.config.orchestration.healthCheckProbe.IP"
);
port = getProfileAgentSettingWithDefault<uint>(port, "agent.config.orchestration.healthCheckProbe.port");
enable = getProfileAgentSettingWithDefault<bool>(enable, "agent.config.orchestration.healthCheckProbe.enable");
ip_address = getConfigurationWithDefault<string>(ip_address, "Health Check", "Probe IP");
port = getConfigurationWithDefault<uint>(port, "Health Check", "Probe port");
enable = getConfigurationWithDefault<bool>(enable, "Health Check", "Probe enabled");
}
void
initConfig()
{
auto prev_ip_address = ip_address;
auto prev_port = port;
initCloudVendorConfig();
max_connections = getProfileAgentSettingWithDefault<uint>(
10,
"agent.config.orchestration.healthCheckProbe.maximunConnections"
);
max_connections = getConfigurationWithDefault<uint>(
max_connections,
"Health Check",
"Probe maximun open connections"
);
max_retry_interval = getProfileAgentSettingWithDefault<uint>(
600,
"agent.config.orchestration.healthCheckProbe.socketReopenPeriod"
);
max_retry_interval = getConfigurationWithDefault<uint>(
max_retry_interval,
"Health Check",
"Probe socket reopen period"
);
if (!enable) {
if (server_sock != -1) closeConnection();
return;
}
if (prev_ip_address != ip_address || prev_port != port) {
if (server_sock != -1) closeConnection();
}
}
void
HandleProbeStartup()
{
size_t next_retry_interval = 1;
while (server_sock == -1) {
next_retry_interval =
next_retry_interval < max_retry_interval ? next_retry_interval*2 : max_retry_interval;
auto socket = i_socket->genSocket(
I_Socket::SocketType::TCP,
false,
true,
"0.0.0.0:" + to_string(port)
);
if (socket.ok()) {
dbgInfo(D_HEALTH_CHECK) << "Successfully created probe listener."
<< " port: "
<< port;
server_sock = socket.unpack();
} else {
dbgWarning(D_HEALTH_CHECK)
<< "Failed to set up socket:"
<< ", Error: "
<< socket.getErr()
<< ", trying again to set up socket in "
<< next_retry_interval
<< " seconds";
i_mainloop->yield(chrono::seconds(next_retry_interval));
}
}
routine_id = i_mainloop->addFileRoutine(
I_MainLoop::RoutineType::RealTime,
server_sock,
[this] () { handleConnection(); },
"Health check probe server",
true
);
}
void
handleConnection()
{
if (open_connections_counter >= max_connections) {
dbgDebug(D_HEALTH_CHECK)
<< "Cannot serve new client, reached maximun open connections bound which is:"
<< open_connections_counter
<< "maximun allowed: "
<< max_connections;
return;
}
Maybe<I_Socket::socketFd> accepted_socket = i_socket->acceptSocket(server_sock, false, ip_address);
if (!accepted_socket.ok()) {
dbgWarning(D_HEALTH_CHECK)
<< "Failed to accept a new client socket: "
<< accepted_socket.getErr();
return;
}
auto new_client_socket = accepted_socket.unpack();
if (new_client_socket <= 0) {
i_socket->closeSocket(new_client_socket);
dbgWarning(D_HEALTH_CHECK)
<< "Failed to initialize communication, generated client socket is OK yet negative";
return;
}
dbgDebug(D_HEALTH_CHECK) << "Successfully accepted client, client fd: " << new_client_socket;
open_connections_counter++;
auto curr_routine = i_mainloop->addFileRoutine(
I_MainLoop::RoutineType::RealTime,
new_client_socket,
[this] ()
{
auto curr_routine_id = i_mainloop->getCurrentRoutineId().unpack();
auto curr_client_socket = client_sockets_routines[curr_routine_id];
auto data_recieved = i_socket->receiveData(curr_client_socket, sizeof(uint8_t), false);
if (!data_recieved.ok()) {
dbgDebug(D_HEALTH_CHECK) << "Connection with client closed, client fd: " << curr_client_socket;
open_connections_counter--;
i_socket->closeSocket(curr_client_socket);
client_sockets_routines.erase(curr_routine_id);
i_mainloop->stop();
}
},
"Health check probe connection handler",
true
);
client_sockets_routines[curr_routine] = new_client_socket;
}
bool enable;
uint max_retry_interval;
unordered_map<I_MainLoop::RoutineID, I_Socket::socketFd> client_sockets_routines;
bool is_first_run = true;
uint open_connections_counter = 0;
uint max_connections = 0;
string ip_address = "";
uint port = 0;
I_Socket::socketFd server_sock = -1;
I_MainLoop::RoutineID routine_id = 0;
I_MainLoop *i_mainloop = nullptr;
I_Socket *i_socket = nullptr;
I_Health_Check_Manager *i_health_check_manager = nullptr;
};
HealthChecker::HealthChecker() : Component("HealthChecker"), pimpl(make_unique<Impl>()) {}
HealthChecker::~HealthChecker() {}
void
HealthChecker::preload()
{
registerExpectedConfiguration<uint>("Health Check", "Probe maximun open connections");
registerExpectedConfiguration<bool>("Health Check", "Probe enabled");
registerExpectedConfiguration<string>("Health Check", "Probe IP");
registerExpectedConfiguration<uint>("Health Check", "Probe port");
registerExpectedConfiguration<uint>("Health Check", "Probe socket reopen period");
registerExpectedSetting<string>("reverseProxy", "cloudVendorName");
}
void
HealthChecker::init()
{
pimpl->init();
}
void
HealthChecker::fini()
{
pimpl->fini();
}

View File

@@ -0,0 +1,7 @@
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
health_check_ut
"health_check_ut.cc"
"health_check;mainloop;singleton;agent_details;config;logging;metric;event_is;health_check_manager;-lboost_regex"
)

View File

@@ -0,0 +1,260 @@
#include "health_checker.h"
#include "cptest.h"
#include "agent_details.h"
#include "mock/mock_logging.h"
#include "mock/mock_time_get.h"
#include "mock/mock_socket_is.h"
#include "mock/mock_mainloop.h"
#include "health_check_manager.h"
#include "config.h"
#include "config_component.h"
#include "singleton.h"
#include "environment.h"
using namespace std;
using namespace testing;
USE_DEBUG_FLAG(D_HEALTH_CHECK);
class HealthCheckerTest : public testing::Test
{
public:
HealthCheckerTest()
{
setConfiguration(true, "Health Check", "Probe enabled");
i_health_check_manager = Singleton::Consume<I_Health_Check_Manager>::from(health_check_manager);
Debug::setUnitTestFlag(D_HEALTH_CHECK, Debug::DebugLevel::TRACE);
Debug::setNewDefaultStdout(&capture_debug);
}
~HealthCheckerTest()
{
Debug::setNewDefaultStdout(&cout);
if (server_socket > 0) {
EXPECT_THAT(capture_debug.str(), HasSubstr("Server socket closed"));
EXPECT_CALL(mock_socket, closeSocket(server_socket));
}
health_checker.fini();
}
ostringstream capture_debug;
StrictMock<MockMainLoop> mock_mainloop;
NiceMock<MockTimeGet> mock_time_get;
::Environment env;
NiceMock<MockLogging> mock_log;
AgentDetails agent_details;
StrictMock<MockSocketIS> mock_socket;
I_Socket::socketFd server_socket = -1;
Context ctx;
ConfigComponent config;
HealthChecker health_checker;
I_MainLoop::Routine connection_handler_routine;
I_MainLoop::Routine client_connection_handler_routine;
I_MainLoop::Routine handle_probe_routine;
//StrictMock<MockHealthCheckManager> mock_health_check_manager;
HealthCheckManager health_check_manager;
I_Health_Check_Manager *i_health_check_manager;
};
TEST_F(HealthCheckerTest, empty)
{
}
TEST_F(HealthCheckerTest, load_policy)
{
health_checker.preload();
health_checker.init();
stringstream config;
config << "{}";
EXPECT_TRUE(Singleton::Consume<Config::I_Config>::from<ConfigComponent>()->loadConfiguration(config));
}
TEST_F(HealthCheckerTest, clientConnection)
{
string ip = "1.2.3.4";
setConfiguration(ip, "Health Check", "Probe IP");
uint port = 11600;
setConfiguration(port, "Health Check", "Probe port");
EXPECT_CALL(
mock_mainloop,
addOneTimeRoutine(I_MainLoop::RoutineType::System, _, _, false)
).WillOnce(DoAll(SaveArg<1>(&handle_probe_routine), Return(0)));
EXPECT_CALL(
mock_socket,
genSocket(I_Socket::SocketType::TCP, false, true, _)
).WillRepeatedly(Return(1));
EXPECT_CALL(
mock_mainloop,
addFileRoutine(I_MainLoop::RoutineType::RealTime, _, _, _, true)
).WillRepeatedly(DoAll(SaveArg<2>(&connection_handler_routine), Return(0)));
int socket = 1;
EXPECT_CALL(mock_socket, acceptSocket(1, false, ip)).WillOnce(Return(socket));
EXPECT_CALL(mock_mainloop, getCurrentRoutineId()).WillRepeatedly(Return(0));
EXPECT_CALL(mock_socket, receiveData(_, 1, false)).WillOnce(Return(vector<char>()));
EXPECT_CALL(mock_socket, closeSocket(socket)).Times(2);
health_checker.init();
handle_probe_routine();
connection_handler_routine();
connection_handler_routine();
health_checker.fini();
}
TEST_F(HealthCheckerTest, loadFromDynamicConfiguration)
{
uint port = 11600;
EXPECT_CALL(
mock_socket,
genSocket(I_Socket::SocketType::TCP, false, true, _)
).WillRepeatedly(Return(1));
EXPECT_CALL(
mock_mainloop,
addFileRoutine(I_MainLoop::RoutineType::RealTime, _, _, _, true)
).WillRepeatedly(DoAll(SaveArg<2>(&connection_handler_routine), Return(0)));
health_checker.init();
health_checker.preload();
EXPECT_THAT(
capture_debug.str(),
HasSubstr(
"Cannot initialize health check component, "
"listening port was not provided. Please provide valid port (>0)."
)
);
setConfiguration(string("1.2.3.4"), "Health Check", "Probe IP");
setConfiguration(port, "Health Check", "Probe port");
}
TEST_F(HealthCheckerTest, connectionsLimit)
{
string ip = "1.2.3.4";
setConfiguration(ip, "Health Check", "Probe IP");
uint port = 11600;
setConfiguration(port, "Health Check", "Probe port");
uint a = 0;
setConfiguration(a, "Health Check", "Probe maximun open connections");
EXPECT_CALL(
mock_mainloop,
addOneTimeRoutine(I_MainLoop::RoutineType::System, _, _, false)
).WillOnce(DoAll(SaveArg<1>(&handle_probe_routine), Return(0)));
EXPECT_CALL(
mock_socket,
genSocket(I_Socket::SocketType::TCP, false, true, _)
).WillRepeatedly(Return(1));
EXPECT_CALL(
mock_mainloop,
addFileRoutine(I_MainLoop::RoutineType::RealTime, _, _, _, true)
).WillRepeatedly(DoAll(SaveArg<2>(&connection_handler_routine), Return(0)));
EXPECT_CALL(mock_mainloop, doesRoutineExist(_)).WillRepeatedly(Return(false));
EXPECT_CALL(mock_socket, acceptSocket(1, false, ip)).WillRepeatedly(Return(1));
EXPECT_CALL(mock_socket, receiveData(_, 1, false)).WillRepeatedly(Return(vector<char>()));
EXPECT_CALL(mock_socket, closeSocket(_)).WillRepeatedly(Return());
health_checker.init();
handle_probe_routine();
connection_handler_routine();
EXPECT_THAT(
capture_debug.str(), HasSubstr("Cannot serve new client, reached maximun open connections")
);
}
TEST_F(HealthCheckerTest, disablingAfterEnabled)
{
string ip = "1.2.3.4";
setConfiguration(ip, "Health Check", "Probe IP");
uint port = 11600;
setConfiguration(port, "Health Check", "Probe port");
EXPECT_CALL(
mock_mainloop,
addOneTimeRoutine(I_MainLoop::RoutineType::System, _, _, false)
).WillOnce(DoAll(SaveArg<1>(&handle_probe_routine), Return(0)));
EXPECT_CALL(
mock_socket,
genSocket(I_Socket::SocketType::TCP, false, true, _)
).WillRepeatedly(Return(1));
EXPECT_CALL(
mock_mainloop,
addFileRoutine(I_MainLoop::RoutineType::RealTime, _, _, _, true)
).WillRepeatedly(DoAll(SaveArg<2>(&connection_handler_routine), Return(0)));
int socket = 1;
EXPECT_CALL(mock_socket, acceptSocket(1, false, ip)).WillOnce(Return(socket));
EXPECT_CALL(mock_mainloop, getCurrentRoutineId()).WillRepeatedly(Return(0));
EXPECT_CALL(mock_socket, receiveData(_, 1, false)).WillOnce(Return(vector<char>()));
EXPECT_CALL(mock_socket, closeSocket(socket)).Times(2);
health_checker.init();
handle_probe_routine();
connection_handler_routine();
connection_handler_routine();
setConfiguration(false, "Health Check", "Probe enabled");
}
TEST_F(HealthCheckerTest, noPort)
{
health_checker.init();
health_checker.preload();
EXPECT_THAT(
capture_debug.str(),
HasSubstr(
"Cannot initialize health check component, "
"listening port was not provided. Please provide valid port (>0)."
)
);
}
TEST_F(HealthCheckerTest, changePortIpConfig)
{
string ip = "1.2.3.4";
setConfiguration(ip, "Health Check", "Probe IP");
uint port = 11600;
setConfiguration(port, "Health Check", "Probe port");
EXPECT_CALL(
mock_mainloop,
addOneTimeRoutine(I_MainLoop::RoutineType::System, _, _, false)
).WillOnce(DoAll(SaveArg<1>(&handle_probe_routine), Return(0)));
EXPECT_CALL(
mock_socket,
genSocket(I_Socket::SocketType::TCP, false, true, _)
).WillRepeatedly(Return(1));
EXPECT_CALL(
mock_mainloop,
addFileRoutine(I_MainLoop::RoutineType::RealTime, _, _, _, true)
).WillRepeatedly(DoAll(SaveArg<2>(&connection_handler_routine), Return(0)));
int socket = 1;
EXPECT_CALL(mock_socket, acceptSocket(1, false, ip)).WillOnce(Return(socket));
EXPECT_CALL(mock_mainloop, getCurrentRoutineId()).WillRepeatedly(Return(0));
EXPECT_CALL(mock_socket, receiveData(_, 1, false)).Times(2).WillRepeatedly(Return(vector<char>()));
EXPECT_CALL(mock_socket, closeSocket(socket)).Times(2);
health_checker.init();
handle_probe_routine();
connection_handler_routine();
connection_handler_routine();
setConfiguration(false, "Health Check", "Probe enabled");
string new_ip = "1.1.1.1";
setConfiguration(new_ip, "Health Check", "Probe IP");
uint new_port = 11111;
setConfiguration(new_port, "Health Check", "Probe port");
connection_handler_routine();
}

View File

@@ -0,0 +1,59 @@
// 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 "hybrid_mode_telemetry.h"
#include "debug.h"
#include "orchestration_comp.h"
#include "i_shell_cmd.h"
#include <algorithm>
using namespace std;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
static inline string &
trim(string &in)
{
in.erase(in.begin(), find_if(in.begin(), in.end(), not1(ptr_fun<int, int>(isspace))));
in.erase(find_if(in.rbegin(), in.rend(), not1(ptr_fun<int, int>(isspace))).base(), in.end());
return in;
}
void
HybridModeMetric::upon(const HybridModeMetricEvent &)
{
auto shell_cmd = Singleton::Consume<I_ShellCmd>::by<OrchestrationComp>();
auto maybe_cmd_output = shell_cmd->getExecOutput(
getFilesystemPathConfig() + "/watchdog/cp-nano-watchdog --restart_count"
);
// get wd process restart count
if (!maybe_cmd_output.ok()) {
dbgWarning(D_ORCHESTRATOR)
<< "Watchdog was unable to provide the process restart count. Error: "
<< maybe_cmd_output.getErr();
return;
}
string cmd_output = maybe_cmd_output.unpack();
trim(cmd_output);
dbgDebug(D_ORCHESTRATOR) << "Watchdog process counter: " << cmd_output;
try {
wd_process_restart.report(stoi(cmd_output));
dbgDebug(D_ORCHESTRATOR) << "Succesfully reported Watchdog process counter: " << cmd_output;
} catch (invalid_argument &) {
dbgWarning(D_ORCHESTRATOR) << "counter value is not a number: " << cmd_output;
} catch (...) {
dbgWarning(D_ORCHESTRATOR) << "Reporting counter value failed with unexpected error";
}
}

View File

@@ -0,0 +1,301 @@
// 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 __FOG_AUTHENTICATOR_H__
#define __FOG_AUTHENTICATOR_H__
#include <chrono>
#include <functional>
#include <tuple>
#include <vector>
#include <algorithm>
#include <map>
#include "cereal/archives/json.hpp"
#include "i_update_communication.h"
#include "i_orchestration_tools.h"
#include "i_agent_details.h"
#include "i_orchestration_status.h"
#include "i_messaging.h"
#include "i_mainloop.h"
#include "i_encryptor.h"
#include "i_details_resolver.h"
#include "i_rest_api.h"
#include "i_time_get.h"
#include "i_encryptor.h"
#include "maybe_res.h"
class FogAuthenticator
:
public I_UpdateCommunication,
Singleton::Consume<I_RestApi>,
Singleton::Consume<I_AgentDetails>,
Singleton::Consume<I_DetailsResolver>,
Singleton::Consume<I_OrchestrationStatus>,
Singleton::Consume<I_OrchestrationTools>,
Singleton::Consume<I_Encryptor>,
Singleton::Consume<I_MainLoop>,
Singleton::Consume<I_Messaging>,
Singleton::Consume<I_TimeGet>
{
class AccessToken
{
public:
AccessToken(const std::string &token, std::chrono::seconds expiration);
std::chrono::seconds getRemainingTime() const;
const std::string & getToken() const { return token; }
uint getExpiration() const { return expiration.count(); }
private:
std::string token;
std::chrono::seconds expiration;
std::chrono::microseconds received_time;
};
class AccessTokenProvider : public ServerRest
{
public:
void doCall() override;
static std::function<Maybe<AccessToken>()> getAccessToken;
private:
S2C_PARAM(std::string, token);
S2C_PARAM(uint, expiration);
};
public:
class RegistrationData
{
enum class AuthenticationType { Token, PresharedSecret, COUNT };
public:
RegistrationData() = default;
RegistrationData(const RegistrationData &) = default;
RegistrationData(const std::string &_env_token);
void serialize(cereal::JSONOutputArchive &out_ar) const;
void serialize(cereal::JSONInputArchive &in_ar);
private:
AuthenticationType type;
std::string data;
};
FogAuthenticator() = default;
~FogAuthenticator() = default;
virtual void init();
static void preload();
Maybe<void> authenticateAgent() override;
void setAddressExtenesion(const std::string &extension) override;
protected:
class UserCredentials
{
public:
UserCredentials() = default;
UserCredentials(const std::string &client_id, const std::string &shared_secret);
std::string getClientId() const { return client_id; }
std::string getSharedSecret() const { return shared_secret; }
void serialize(cereal::JSONOutputArchive &out_ar) const;
void serialize(cereal::JSONInputArchive &in_ar);
private:
std::string client_id;
std::string shared_secret;
};
void loadRequiredSecurityApps();
Maybe<AccessToken> getAccessToken(const UserCredentials &credentials) const;
Maybe<UserCredentials>
registerAgent(
const RegistrationData &reg_data,
const std::string &name,
const std::string &type,
const std::string &platform,
const std::string &architecture
) const;
void initRestAPI();
Maybe<UserCredentials> getCredentials();
bool saveCredentialsToFile(const UserCredentials &credentials) const;
Maybe<UserCredentials> getCredentialsFromFile() const;
Maybe<RegistrationData> getRegistrationData();
std::string base64Encode(const std::string &in) const;
std::string buildBasicAuthHeader(const std::string &username, const std::string &pass) const;
std::string buildOAuth2Header(const std::string &token) const;
// This apps which the orchestrations requires them from Fog.
std::vector<std::string> required_security_apps;
std::string fog_address_ex = "";
std::string filesystem_prefix = "";
std::string otp = "";
Maybe<UserCredentials> credentials = genError("User credentials are empty");
Maybe<AccessToken> access_token = genError("Access token was not received yet");
Maybe<RegistrationData> reg_data = genError("Registration data is empty");
I_MainLoop::RoutineID routine = 0;
};
class AdditionalMetaData
{
public:
AdditionalMetaData() = default;
AdditionalMetaData(const AdditionalMetaData &) = default;
AdditionalMetaData &
operator<<(const std::pair<std::string, std::string> &data)
{
additional_data.insert(data);
return *this;
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
for (auto &data : additional_data) {
out_ar(cereal::make_nvp(data.first, data.second));
}
}
private:
std::map<std::string, std::string> additional_data;
};
class RegistrationRequest : public ClientRest
{
private:
class MetaData
{
public:
MetaData() = default;
MetaData(
const std::string &_name,
const std::string &_type,
const std::string &_platform,
const std::string &_architecture,
const std::string &_agent_version)
:
name(_name),
type(_type),
platform(_platform),
architecture(_architecture),
agent_version(_agent_version)
{
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("agentName", name),
cereal::make_nvp("agentType", type),
cereal::make_nvp("platform", platform),
cereal::make_nvp("architecture", architecture),
cereal::make_nvp("agentVersion", agent_version),
cereal::make_nvp("additionalMetaData", additional_metadata)
);
}
AdditionalMetaData &
operator<<(const std::pair<std::string, std::string> &data)
{
return additional_metadata << data;
}
private:
std::string name;
std::string type;
std::string platform;
std::string architecture;
std::string agent_version;
AdditionalMetaData additional_metadata;
};
public:
RegistrationRequest(
const FogAuthenticator::RegistrationData &reg_data,
const std::string &name,
const std::string &type,
const std::string &platform,
const std::string &architecture,
const std::string &agent_version)
:
authenticationData({ reg_data }),
metaData(MetaData(name, type, platform, architecture, agent_version))
{
}
AdditionalMetaData &
operator<<(const std::pair<std::string, std::string> &data)
{
return metaData.get() << data;
}
std::string getClientId() const { return client_id; }
std::string getSharedSecret() const { return shared_secret; }
std::string getAgentId() const { return agentId; }
std::string getProfileId() const { return profileId; }
std::string getTenantId() const { return tenantId; }
private:
C2S_PARAM(std::vector<FogAuthenticator::RegistrationData>, authenticationData);
C2S_PARAM(MetaData, metaData);
S2C_PARAM(std::string, client_id);
S2C_PARAM(std::string, shared_secret);
S2C_PARAM(std::string, tenantId);
S2C_PARAM(std::string, profileId);
S2C_PARAM(std::string, agentId);
};
class PolicyVersionPatchRequest : public ClientRest
{
public:
PolicyVersionPatchRequest(const std::string &_policy_version)
:
policy_version(_policy_version)
{
}
private:
C2S_LABEL_PARAM(std::string, policy_version, "policyVersion");
};
class TokenRequest : public ClientRest
{
public:
std::string getAccessToken() const { return access_token; }
std::string getTokenType() const { return token_type; }
std::string getUserId() const { return user_id; }
std::string getScope() const { return scope; }
std::string getJTI() const { return jti; }
int getExpirationTime() const { return expires_in; }
private:
S2C_PARAM(int, expires_in);
S2C_PARAM(std::string, jti);
S2C_PARAM(std::string, scope);
S2C_PARAM(std::string, token_type);
S2C_PARAM(std::string, access_token);
S2C_LABEL_PARAM(std::string, user_id, "uuid");
};
#endif // __FOG_AUTHENTICATOR_H__

View File

@@ -0,0 +1,45 @@
// 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 __FOG_COMMUNICATION_H__
#define __FOG_COMMUNICATION_H__
#include <chrono>
#include <functional>
#include <tuple>
#include <vector>
#include "cereal/archives/json.hpp"
#include "i_update_communication.h"
#include "fog_authenticator.h"
#include "i_orchestration_tools.h"
#include "i_agent_details.h"
#include "i_orchestration_status.h"
#include "i_messaging.h"
#include "i_mainloop.h"
#include "i_encryptor.h"
#include "i_details_resolver.h"
#include "i_rest_api.h"
#include "i_time_get.h"
#include "i_encryptor.h"
#include "maybe_res.h"
class FogCommunication : public FogAuthenticator
{
public:
Maybe<void> getUpdate(CheckUpdateRequest &request) override;
Maybe<std::string> downloadAttributeFile(const GetResourceFile &resourse_file) override;
Maybe<void> sendPolicyVersion(const std::string &policy_version) const override;
};
#endif // __FOG_COMMUNICATION_H__

View File

@@ -0,0 +1,91 @@
// 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 __GET_STATUS_RES_H__
#define __GET_STATUS_RES_H__
#include "i_messaging_downloader.h"
#include "i_messaging.h"
#include "i_mainloop.h"
#include "i_shell_cmd.h"
#include "i_encryptor.h"
#include "i_orchestration_status.h"
#include "i_rest_api.h"
#include "i_orchestration_tools.h"
#include "i_downloader.h"
#include "i_service_controller.h"
#include "i_manifest_controller.h"
#include "i_update_communication.h"
#include "i_details_resolver.h"
#include "i_shell_cmd.h"
#include "i_agent_details.h"
#include "i_environment.h"
#include "i_tenant_manager.h"
#include "i_package_handler.h"
#include "component.h"
class getStatusRest : public ServerRest
{
public:
void
doCall() override
{
auto i_orch_status = Singleton::Consume<I_OrchestrationStatus>::by<OrchestrationComp>();
policies = "";
settings = "";
for (auto &policy: i_orch_status->getServicePolicies()) {
policies = policies.get() + "\n " + policy.first + ": " + policy.second;
}
for (auto &setting: i_orch_status->getServiceSettings()) {
settings = settings.get() + "\n " + setting.first + ": " + setting.second;
}
last_update_attempt = i_orch_status->getLastUpdateAttempt();
last_update = i_orch_status->getUpdateTime();
last_update_status = i_orch_status->getUpdateStatus();
policy_version = i_orch_status->getPolicyVersion();
last_policy_update = i_orch_status->getLastPolicyUpdate();
last_manifest_update = i_orch_status->getLastManifestUpdate();
last_settings_update = i_orch_status->getLastSettingsUpdate();
registration_status = i_orch_status->getRegistrationStatus();
manifest_status = i_orch_status->getManifestStatus();
upgrade_mode = i_orch_status->getUpgradeMode();
fog_address = i_orch_status->getFogAddress();
agent_id = i_orch_status->getAgentId();
profile_id = i_orch_status->getProfileId();
tenant_id = i_orch_status->getTenantId();
registration_details = i_orch_status->getRegistrationDetails();
}
private:
S2C_LABEL_PARAM(std::string, last_update_attempt, "Last update attempt");
S2C_LABEL_PARAM(std::string, last_update, "Last update");
S2C_LABEL_PARAM(std::string, last_update_status, "Last update status");
S2C_LABEL_PARAM(std::string, policy_version, "Policy version");
S2C_LABEL_PARAM(std::string, last_policy_update, "Last policy update");
S2C_LABEL_PARAM(std::string, last_manifest_update, "Last manifest update");
S2C_LABEL_PARAM(std::string, last_settings_update, "Last settings update");
S2C_LABEL_PARAM(std::string, registration_status, "Registration status");
S2C_LABEL_PARAM(std::string, manifest_status, "Manifest status");
S2C_LABEL_PARAM(std::string, upgrade_mode, "Upgrade mode");
S2C_LABEL_PARAM(std::string, fog_address, "Fog address");
S2C_LABEL_PARAM(std::string, agent_id, "Agent ID");
S2C_LABEL_PARAM(std::string, profile_id, "Profile ID");
S2C_LABEL_PARAM(std::string, tenant_id, "Tenant ID");
S2C_LABEL_PARAM(std::string, registration_details, "Registration details");
S2C_LABEL_PARAM(std::string, policies, "Service policy");
S2C_LABEL_PARAM(std::string, settings, "Service settings");
};
#endif // __GET_STATUS_RES_H__

View File

@@ -0,0 +1,58 @@
// 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 __HYBRID_COMMUNICATION_H__
#define __HYBRID_COMMUNICATION_H__
#include <chrono>
#include <functional>
#include <tuple>
#include <vector>
#include "cereal/archives/json.hpp"
#include "singleton.h"
#include "i_update_communication.h"
#include "fog_authenticator.h"
#include "i_k8s_policy_gen.h"
#include "i_orchestration_tools.h"
#include "i_agent_details.h"
#include "i_orchestration_status.h"
#include "i_messaging.h"
#include "i_mainloop.h"
#include "i_encryptor.h"
#include "i_details_resolver.h"
#include "i_rest_api.h"
#include "i_time_get.h"
#include "i_encryptor.h"
#include "maybe_res.h"
class HybridCommunication
:
public FogAuthenticator,
Singleton::Consume<I_K8S_Policy_Gen>
{
public:
virtual void init() override;
Maybe<void> getUpdate(CheckUpdateRequest &request) override;
Maybe<std::string> downloadAttributeFile(const GetResourceFile &resourse_file) override;
Maybe<void> sendPolicyVersion(const std::string &policy_version) const override;
std::string getChecksum(const std::string &policy_version);
private:
Maybe<std::string> getNewVersion();
std::string curr_version;
std::string curr_policy;
};
#endif // __HYBRID_COMMUNICATION_H__

View File

@@ -0,0 +1,43 @@
// 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 __LOCAL_COMMUNICATION_H__
#define __LOCAL_COMMUNICATION_H__
#include "i_update_communication.h"
#include "i_orchestration_tools.h"
#include "maybe_res.h"
class LocalCommunication
:
public I_UpdateCommunication,
Singleton::Consume<I_OrchestrationTools>
{
public:
static void preload();
void init();
Maybe<void> authenticateAgent() override;
Maybe<void> getUpdate(CheckUpdateRequest &request) override;
Maybe<std::string> downloadAttributeFile(const GetResourceFile &resourse_file) override;
void setAddressExtenesion(const std::string &extension) override;
Maybe<void> sendPolicyVersion(const std::string &policy_version) const override;
private:
std::string getChecksum(const std::string &file_path);
std::string filesystem_prefix = "";
};
#endif // __LOCAL_COMMUNICATION_H__

View File

@@ -0,0 +1,46 @@
// 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 __MOCK_DETAILS_RESOLVER_H__
#define __MOCK_DETAILS_RESOLVER_H__
#include <iostream>
#include "i_details_resolver.h"
#include "cptest.h"
#include "maybe_res.h"
std::ostream &
operator<<(std::ostream &os, const Maybe<std::tuple<std::string, std::string, std::string>> &)
{
return os;
}
class MockDetailsResolver
:
public Singleton::Provide<I_DetailsResolver>::From<MockProvider<I_DetailsResolver>>
{
public:
MOCK_METHOD0(getHostname, Maybe<std::string>());
MOCK_METHOD0(getPlatform, Maybe<std::string>());
MOCK_METHOD0(getArch, Maybe<std::string>());
MOCK_METHOD0(getAgentVersion, std::string());
MOCK_METHOD0(isReverseProxy, bool());
MOCK_METHOD0(isKernelVersion3OrHigher, bool());
MOCK_METHOD0(isGwNotVsx, bool());
MOCK_METHOD0(getResolvedDetails, std::map<std::string, std::string>());
MOCK_METHOD0(isVersionEqualOrAboveR8110, bool());
MOCK_METHOD0(parseNginxMetadata, Maybe<std::tuple<std::string, std::string, std::string>>());
};
#endif // __MOCK_DETAILS_RESOLVER_H__

View File

@@ -0,0 +1,42 @@
// 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 __MOCK_DOWNLOADER_H__
#define __MOCK_DOWNLOADER_H__
#include "cptest.h"
#include "i_downloader.h"
#include <string>
class MockDownloader :
public Singleton::Provide<I_Downloader>::From<MockProvider<I_Downloader>>
{
public:
MOCK_CONST_METHOD3(
downloadFileFromFog,
Maybe<std::string>(const std::string &, Package::ChecksumTypes, const GetResourceFile &)
);
MOCK_CONST_METHOD2(
downloadVirtualFileFromFog,
Maybe<std::map<std::string, std::string>>(const GetResourceFile &, Package::ChecksumTypes)
);
MOCK_CONST_METHOD4(
downloadFileFromURL,
Maybe<std::string>(const std::string &, const std::string &, Package::ChecksumTypes, const std::string &)
);
};
#endif // __MOCK_DOWNLOADER_H__

View File

@@ -0,0 +1,28 @@
// 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 __MOCK_MANIFEST_CONTROLLER_H__
#define __MOCK_MANIFEST_CONTROLLER_H__
#include "i_manifest_controller.h"
#include "cptest.h"
class MockManifestController :
public Singleton::Provide<I_ManifestController>::From<MockProvider<I_ManifestController>>
{
public:
MOCK_METHOD1(updateManifest, bool(const std::string &));
MOCK_METHOD0(loadAfterSelfUpdate, bool());
};
#endif // __MOCK_MANIFEST_CONTROLLER_H__

View 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 __MOCK_MESSAGING_DOWNLOADER_H__
#define __MOCK_MESSAGING_DOWNLOADER_H__
#include "cptest.h"
#include <string>
#include "i_messaging_downloader.h"
class MockMessagingDownloader
:
public Singleton::Provide<I_MessagingDownloader>::From<MockProvider<I_MessagingDownloader>>
{
public:
MOCK_METHOD4(
downloadFile,
bool(
const std::string &,
const std::string &,
OnCompleteCB,
const unsigned int
)
);
};
#endif // __MOCK_MESSAGING_DOWNLOADER_H__

View File

@@ -0,0 +1,63 @@
// 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 __MOCK_ORCHESTRATION_STATUS_H__
#define __MOCK_ORCHESTRATION_STATUS_H__
#include "i_orchestration_status.h"
#include "cptest.h"
class MockOrchestrationStatus
:
public Singleton::Provide<I_OrchestrationStatus>::From<MockProvider<I_OrchestrationStatus>>
{
public:
MOCK_METHOD0(writeStatusToFile, void());
MOCK_METHOD0(recoverFields, void());
MOCK_METHOD1(setUpgradeMode, void(const std::string &));
MOCK_METHOD1(setAgentType, void(const std::string &));
MOCK_METHOD1(setRegistrationStatus, void(const std::string &));
MOCK_METHOD1(setFogAddress, void(const std::string &));
MOCK_METHOD1(setPolicyVersion, void(const std::string &));
MOCK_METHOD1(setIsConfigurationUpdated, void(EnumArray<OrchestrationStatusConfigType, bool> config_types));
MOCK_METHOD0(setLastUpdateAttempt, void());
MOCK_METHOD3(setAgentDetails, void(const std::string &, const std::string &, const std::string &));
MOCK_METHOD3(setFieldStatus,
void(const OrchestrationStatusFieldType &, const OrchestrationStatusResult &, const std::string &));
MOCK_METHOD4(setRegistrationDetails,
void(const std::string &, const std::string &, const std::string &, const std::string &)
);
MOCK_METHOD3(setServiceConfiguration,
void(const std::string &, const std::string &, const OrchestrationStatusConfigType &)
);
MOCK_CONST_METHOD0(getLastUpdateAttempt, const std::string&());
MOCK_CONST_METHOD0(getUpdateStatus, const std::string&());
MOCK_CONST_METHOD0(getUpdateTime, const std::string&());
MOCK_CONST_METHOD0(getLastManifestUpdate, const std::string&());
MOCK_CONST_METHOD0(getPolicyVersion, const std::string&());
MOCK_CONST_METHOD0(getLastPolicyUpdate, const std::string&());
MOCK_CONST_METHOD0(getLastSettingsUpdate, const std::string&());
MOCK_CONST_METHOD0(getUpgradeMode, const std::string&());
MOCK_CONST_METHOD0(getFogAddress, const std::string&());
MOCK_CONST_METHOD0(getRegistrationStatus, const std::string&());
MOCK_CONST_METHOD0(getAgentId, const std::string&());
MOCK_CONST_METHOD0(getProfileId, const std::string&());
MOCK_CONST_METHOD0(getTenantId, const std::string&());
MOCK_CONST_METHOD0(getManifestStatus, const std::string&());
MOCK_CONST_METHOD0(getManifestError, const std::string&());
MOCK_CONST_METHOD0(getServicePolicies, const std::map<std::string, std::string>&());
MOCK_CONST_METHOD0(getServiceSettings, const std::map<std::string, std::string>&());
MOCK_CONST_METHOD0(getRegistrationDetails, const std::string());
};
#endif // __MOCK_ORCHESTRATION_STATUS_H__

View File

@@ -0,0 +1,58 @@
// 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 __MOCK_ORCHESTRATION_TOOLS_H__
#define __MOCK_ORCHESTRATION_TOOLS_H__
#include "cptest.h"
#include "i_orchestration_tools.h"
template <typename T>
std::ostream &
operator<<(std::ostream &os, const std::vector<T> &)
{
return os;
}
template <typename T, typename S>
std::ostream &
operator<<(std::ostream &os, const std::map<T, S> &)
{
return os;
}
class MockOrchestrationTools
:
public Singleton::Provide<I_OrchestrationTools>::From<MockProvider<I_OrchestrationTools>>
{
public:
MOCK_CONST_METHOD1(loadPackagesFromJson, Maybe<std::map<std::string, Package>>(const std::string &));
MOCK_CONST_METHOD2(packagesToJsonFile, bool(const std::map<std::string, Package> &, const std::string &));
MOCK_CONST_METHOD1(isNonEmptyFile, bool(const std::string &));
MOCK_CONST_METHOD1(readFile, Maybe<std::string>(const std::string &));
MOCK_CONST_METHOD2(writeFile, bool(const std::string &, const std::string &));
MOCK_CONST_METHOD1(removeFile, bool(const std::string &));
MOCK_CONST_METHOD2(copyFile, bool(const std::string &, const std::string &));
MOCK_CONST_METHOD2(calculateChecksum, Maybe<std::string>(Package::ChecksumTypes, const std::string &));
MOCK_CONST_METHOD2(
jsonObjectSplitter,
Maybe<std::map<std::string, std::string>>(const std::string &, const std::string &)
);
MOCK_CONST_METHOD1(doesFileExist, bool(const std::string &));
MOCK_CONST_METHOD1(createDirectory, bool(const std::string &));
MOCK_CONST_METHOD1(doesDirectoryExist, bool(const std::string &));
MOCK_CONST_METHOD1(executeCmd, bool(const std::string &));
MOCK_CONST_METHOD1(base64Encode, std::string(const std::string &));
MOCK_CONST_METHOD1(base64Decode, std::string(const std::string &));
};
#endif // __MOCK_ORCHESTRATION_TOOLS_H__

View File

@@ -0,0 +1,31 @@
// 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 __MOCK_PACKAGE_HANDLER_H__
#define __MOCK_PACKAGE_HANDLER_H__
#include "i_package_handler.h"
#include "cptest.h"
class MockPackageHandler :
public Singleton::Provide<I_PackageHandler>::From<MockProvider<I_PackageHandler>>
{
public:
MOCK_CONST_METHOD3(installPackage, bool(const std::string &, const std::string &, bool));
MOCK_CONST_METHOD3(uninstallPackage, bool(const std::string &, const std::string &, const std::string &));
MOCK_CONST_METHOD2(preInstallPackage, bool(const std::string &, const std::string &));
MOCK_CONST_METHOD2(postInstallPackage, bool(const std::string &, const std::string &));
MOCK_CONST_METHOD2(updateSavedPackage, bool(const std::string &, const std::string &));
MOCK_CONST_METHOD2(shouldInstallPackage, bool(const std::string &, const std::string &));
};
#endif // __MOCK_PACKAGE_HANDLER_H__

View File

@@ -0,0 +1,62 @@
// 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 __MOCK_SERVICE_CONTROLLER_H__
#define __MOCK_SERVICE_CONTROLLER_H__
#include "i_service_controller.h"
#include "cptest.h"
#include <string>
class MockServiceController :
public Singleton::Provide<I_ServiceController>::From<MockProvider<I_ServiceController>>
{
public:
MOCK_CONST_METHOD0(getPolicyVersion, const std::string &());
MOCK_CONST_METHOD0(getUpdatePolicyVersion, const std::string &());
MOCK_METHOD4(
updateServiceConfiguration,
bool(
const std::string &new_policy_path,
const std::string &new_settings_path,
const std::vector<std::string> &new_data_files,
const std::string &tenant_id
)
);
MOCK_METHOD1(isServiceInstalled, bool(const std::string &service_name));
MOCK_METHOD4(
registerServiceConfig,
void(
const std::string &service_name,
PortNumber listening_port,
const std::vector<std::string> &expected_configurations,
const std::string &id
)
);
typedef std::map<std::string, PortNumber> ServicePortMap;
MOCK_METHOD0(getServiceToPortMap, ServicePortMap());
MOCK_METHOD2(updateReconfStatus, void(int id, ReconfStatus status));
MOCK_METHOD4(
startReconfStatus,
void(int id, ReconfStatus status, const std::string &serivce_name, const std::string &service_id)
);
};
#endif // __MOCK_SERVICE_CONTROLLER_H__

View File

@@ -0,0 +1,37 @@
// 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 __MOCK_UPDATE_COMMUNICATION_H__
#define __MOCK_UPDATE_COMMUNICATION_H__
#include "i_update_communication.h"
#include "cptest.h"
std::ostream &
operator<<(std::ostream &os, const CheckUpdateRequest &)
{
return os;
}
class MockUpdateCommunication :
public Singleton::Provide<I_UpdateCommunication>::From<MockProvider<I_UpdateCommunication>>
{
public:
MOCK_METHOD0(authenticateAgent, Maybe<void>());
MOCK_METHOD1(getUpdate, Maybe<void>(CheckUpdateRequest &));
MOCK_METHOD1(downloadAttributeFile, Maybe<std::string>(const GetResourceFile &));
MOCK_METHOD1(setAddressExtenesion, void(const std::string &));
MOCK_CONST_METHOD1(sendPolicyVersion, Maybe<void>(const std::string &));
};
#endif // __MOCK_UPDATE_COMMUNICATION_H__

View File

@@ -0,0 +1,38 @@
// 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 __ORCHESTRATION_POLICY_H__
#define __ORCHESTRATION_POLICY_H__
#include <string>
#include "cereal/archives/json.hpp"
class OrchestrationPolicy
{
public:
const std::string & getFogAddress() const;
const unsigned long & getSleepInterval() const;
const unsigned long & getErrorSleepInterval() const;
void serialize(cereal::JSONInputArchive & archive);
bool operator==(const OrchestrationPolicy &other) const;
bool operator!=(const OrchestrationPolicy &other) const;
private:
std::string fog_address;
unsigned long sleep_interval;
unsigned long error_sleep_interval;
};
#endif // __ORCHESTRATION_POLICY_H__

View File

@@ -0,0 +1 @@
add_library(k8s_policy_gen k8s_policy_gen.cc)

View File

@@ -0,0 +1,768 @@
// 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 __APPSEC_PRACTICE_SECTION_H__
#define __APPSEC_PRACTICE_SECTION_H__
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
#include "customized_cereal_map.h"
#include "k8s_policy_common.h"
#include "triggers_section.h"
#include "trusted_sources_section.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class AppSecWebBotsURI
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Web Bots URI";
parseAppsecJSONKey<std::string>("uri", uri, archive_in);
}
const std::string & getURI() const { return uri; }
private:
std::string uri;
};
std::ostream &
operator<<(std::ostream &os, const AppSecWebBotsURI &obj)
{
os << obj.getURI();
return os;
}
std::ostream &
operator<<(std::ostream &os, const std::vector<AppSecWebBotsURI> &obj)
{
os << "[" << std::endl;
makeSeparatedStr(obj, ",");
os << std::endl << "]";
return os;
}
class AppSecPracticeAntiBot
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Web Bots";
parseAppsecJSONKey<std::vector<AppSecWebBotsURI>>("injected-URIs", injected_uris, archive_in);
parseAppsecJSONKey<std::vector<AppSecWebBotsURI>>("validated-URIs", validated_uris, archive_in);
parseAppsecJSONKey<std::string>("override-mode", override_mode, archive_in, "Inactive");
}
void
save(cereal::JSONOutputArchive &out_ar) const
{
std::vector<std::string> injected;
std::vector<std::string> validated;
for (const AppSecWebBotsURI &uri : getInjectedURIs()) injected.push_back(uri.getURI());
for (const AppSecWebBotsURI &uri : getValidatedURIs()) injected.push_back(uri.getURI());
out_ar(
cereal::make_nvp("injected", injected),
cereal::make_nvp("validated", validated)
);
}
const std::vector<AppSecWebBotsURI> & getInjectedURIs() const { return injected_uris; }
const std::vector<AppSecWebBotsURI> & getValidatedURIs() const { return validated_uris; }
const std::string & getOverrideMode() const { return override_mode; }
private:
std::string override_mode;
std::vector<AppSecWebBotsURI> injected_uris;
std::vector<AppSecWebBotsURI> validated_uris;
};
std::ostream &
operator<<(std::ostream &os, const AppSecPracticeAntiBot &obj)
{
os
<< "injected-URIs: "
<< obj.getInjectedURIs()
<< " validated-URIs: "
<< obj.getValidatedURIs()
<< ", override_mode: "
<< obj.getOverrideMode();
return os;
}
class AppSecWebAttackProtections
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Web Attack Protections";
parseAppsecJSONKey<std::string>("csrf-protection", csrf_protection, archive_in, "Inactive");
parseAppsecJSONKey<std::string>("error-disclosure", error_disclosure, archive_in, "Inactive");
parseAppsecJSONKey<std::string>("open-redirect", open_redirect, archive_in, "Inactive");
parseAppsecJSONKey<bool>("non-valid-http-methods", non_valid_http_methods, archive_in, false);
}
const std::string
getCsrfProtectionMode() const
{
if (key_to_practices_val.find(csrf_protection) == key_to_practices_val.end()) {
dbgError(D_K8S_POLICY)
<< "Failed to find a value for "
<< csrf_protection
<< ". Setting CSRF protection to Inactive";
return "Inactive";
}
return key_to_practices_val.at(csrf_protection);
}
const std::string & getErrorDisclosureMode() const { return error_disclosure; }
bool getNonValidHttpMethods() const { return non_valid_http_methods; }
const std::string
getOpenRedirectMode() const
{
if (key_to_practices_val.find(open_redirect) == key_to_practices_val.end()) {
dbgError(D_K8S_POLICY)
<< "Failed to find a value for "
<< open_redirect
<< ". Setting Open Redirect mode to Inactive";
return "Inactive";
}
return key_to_practices_val.at(open_redirect);
}
private:
std::string csrf_protection;
std::string open_redirect;
std::string error_disclosure;
bool non_valid_http_methods;
};
std::ostream &
operator<<(std::ostream &os, const AppSecWebAttackProtections &obj)
{
os
<< " csrf-protection: "
<< obj.getCsrfProtectionMode()
<< " error-disclosure: "
<< obj.getErrorDisclosureMode()
<< " non-valid-http-methods: "
<< obj.getNonValidHttpMethods()
<< " open-redirect: "
<< obj.getOpenRedirectMode();
return os;
}
class AppSecPracticeWebAttacks
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec practice spec";
parseAppsecJSONKey<AppSecWebAttackProtections>("protections", protections, archive_in);
parseAppsecJSONKey<std::string>("minimum-confidence", minimum_confidence, archive_in, "critical");
parseAppsecJSONKey<std::string>("override-mode", mode, archive_in, "Unset");
parseAppsecJSONKey<int>("max-body-size-kb", max_body_size_kb, archive_in, 1000000);
parseAppsecJSONKey<int>("max-header-size-bytes", max_header_size_bytes, archive_in, 102400);
parseAppsecJSONKey<int>("max-object-depth", max_object_depth, archive_in, 40);
parseAppsecJSONKey<int>("max-url-size-bytes", max_url_size_bytes, archive_in, 32768);
}
int getMaxBodySizeKb() const { return max_body_size_kb; }
int getMaxHeaderSizeBytes() const { return max_header_size_bytes; }
int getMaxObjectDepth() const { return max_object_depth; }
int getMaxUrlSizeBytes() const { return max_url_size_bytes; }
const std::string & getMinimumConfidence() const { return minimum_confidence; }
const AppSecWebAttackProtections & getprotections() const { return protections; }
const std::string &
getMode(const std::string &default_mode = "Inactive") const
{
if (mode == "Unset" || (key_to_practices_val.find(mode) == key_to_practices_val.end())) {
dbgError(D_K8S_POLICY) << "Couldn't find a value for key: " << mode << ". Returning " << default_mode;
return default_mode;
}
return key_to_practices_val.at(mode);
}
private:
int max_body_size_kb;
int max_header_size_bytes;
int max_object_depth;
int max_url_size_bytes;
std::string minimum_confidence;
std::string mode;
AppSecWebAttackProtections protections;
};
std::ostream &
operator<<(std::ostream &os, const AppSecPracticeWebAttacks &obj)
{
os
<< "mode: "
<< obj.getMode()
<< " max-body-size-kb: "
<< obj.getMaxBodySizeKb()
<< " max-header-size-bytes: "
<< obj.getMaxHeaderSizeBytes()
<< " max-object-depth: "
<< obj.getMaxObjectDepth()
<< " max-url-size-bytes: "
<< obj.getMaxUrlSizeBytes()
<< " minimum-confidence: "
<< obj.getMinimumConfidence()
<< " protections: "
<< obj.getprotections();
return os;
}
class AppSecPracticeSnortSignatures
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Snort Signatures practice";
parseAppsecJSONKey<std::string>("override-mode", override_mode, archive_in, "Inactive");
parseAppsecJSONKey<std::vector<std::string>>("configmap", config_map, archive_in);
}
const std::string & getOverrideMode() const { return override_mode; }
const std::vector<std::string> & getConfigMap() const { return config_map; }
private:
std::string override_mode;
std::vector<std::string> config_map;
};
std::ostream &
operator<<(std::ostream &os, const AppSecPracticeSnortSignatures &obj)
{
os
<< "override mode: "
<< obj.getOverrideMode()
<< ". Config map: [" << std::endl
<< makeSeparatedStr(obj.getConfigMap(), ",")
<< std::endl << "]";
return os;
}
class AppSecPracticeOpenSchemaAPI
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSecPracticeOpenSchemaAPI practice";
parseAppsecJSONKey<std::string>("override-mode", override_mode, archive_in, "Inactive");
parseAppsecJSONKey<std::vector<std::string>>("configmap", config_map, archive_in);
}
const std::string & getOverrideMode() const { return override_mode; }
const std::vector<std::string> & getConfigMap() const { return config_map; }
private:
std::string override_mode;
std::vector<std::string> config_map;
};
std::ostream &
operator<<(std::ostream &os, const AppSecPracticeOpenSchemaAPI &obj)
{
os
<< "override mode: "
<< obj.getOverrideMode()
<< ". Config map: [" << std::endl
<< makeSeparatedStr(obj.getConfigMap(), ",")
<< std::endl << "]";
return os;
}
class AppSecPracticeSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec practice spec";
parseAppsecJSONKey<AppSecPracticeOpenSchemaAPI>(
"openapi-schema-validation",
openapi_schema_validation,
archive_in
);
parseAppsecJSONKey<AppSecPracticeSnortSignatures>("snort-signatures", snort_signatures, archive_in);
parseAppsecJSONKey<AppSecPracticeWebAttacks>("web-attacks", web_attacks, archive_in);
parseAppsecJSONKey<AppSecPracticeAntiBot>("anti-bot", anti_bot, archive_in);
}
const AppSecPracticeOpenSchemaAPI & getOpenSchemaValidation() const { return openapi_schema_validation; }
const AppSecPracticeSnortSignatures & getSnortSignatures() const { return snort_signatures; }
const AppSecPracticeWebAttacks & getWebAttacks() const { return web_attacks; }
const AppSecPracticeAntiBot & getAntiBot() const { return anti_bot; }
private:
AppSecPracticeOpenSchemaAPI openapi_schema_validation;
AppSecPracticeSnortSignatures snort_signatures;
AppSecPracticeWebAttacks web_attacks;
AppSecPracticeAntiBot anti_bot;
};
std::ostream &
operator<<(std::ostream &os, const AppSecPracticeSpec &obj)
{
os
<< "Open Schema API:" << std::endl
<< obj.getOpenSchemaValidation()
<< std::endl << "Snort Signatures:" << std::endl
<< obj.getOpenSchemaValidation()
<< std::endl << "Web Attacks:" << std::endl
<< obj.getWebAttacks()
<< std::endl << "Web Bots:" << std::endl
<< obj.getAntiBot();
return os;
}
class PracticeAdvancedConfig
{
public:
PracticeAdvancedConfig(const AppSecPracticeSpec &parsed_appsec_spec)
:
http_header_max_size(parsed_appsec_spec.getWebAttacks().getMaxHeaderSizeBytes()),
http_illegal_methods_allowed(0),
http_request_body_max_size(parsed_appsec_spec.getWebAttacks().getMaxBodySizeKb()),
json_max_object_depth(parsed_appsec_spec.getWebAttacks().getMaxObjectDepth()),
url_max_size(parsed_appsec_spec.getWebAttacks().getMaxUrlSizeBytes())
{}
void
save(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("httpHeaderMaxSize", http_header_max_size),
cereal::make_nvp("httpIllegalMethodsAllowed", http_illegal_methods_allowed),
cereal::make_nvp("httpRequestBodyMaxSize", http_request_body_max_size),
cereal::make_nvp("jsonMaxObjectDepth", json_max_object_depth),
cereal::make_nvp("urlMaxSize", url_max_size)
);
}
void setIllegalMethodsAllowed(int val) { http_illegal_methods_allowed = val; };
private:
int http_header_max_size;
int http_illegal_methods_allowed;
int http_request_body_max_size;
int json_max_object_depth;
int url_max_size;
};
class TriggersInWaapSection
{
public:
TriggersInWaapSection(const LogTriggerSection &log_section)
:
trigger_type("log"),
id(log_section.getTriggerId()),
name(log_section.getTriggerName()),
log(log_section)
{}
void
save(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("$triggerType", trigger_type),
cereal::make_nvp("id", id),
cereal::make_nvp("name", name),
cereal::make_nvp("log", log)
);
}
private:
std::string trigger_type;
std::string id;
std::string name;
LogTriggerSection log;
};
class AppSecOverride
{
public:
AppSecOverride(const SourcesIdentifiers &parsed_trusted_sources)
{
std::string source_ident = parsed_trusted_sources.getSourceIdent();
std::map<std::string, std::string> behavior = {{"httpSourceId", source_ident}};
parsed_behavior.push_back(behavior);
parsed_match = {{"operator", "BASIC"}, {"tag", "sourceip"}, {"value", "0.0.0.0/0"}};
}
void
save(cereal::JSONOutputArchive &out_ar) const
{
std::string parameter_type = "TrustedSource";
out_ar(
cereal::make_nvp("parsedBehavior", parsed_behavior),
cereal::make_nvp("parsedMatch", parsed_match)
);
}
private:
std::vector<std::map<std::string, std::string>> parsed_behavior;
std::map<std::string, std::string> parsed_match;
};
class WebAppSection
{
public:
WebAppSection(
const std::string &_application_urls,
const std::string &_asset_id,
const std::string &_asset_name,
const std::string &_rule_id,
const std::string &_rule_name,
const std::string &_practice_id,
const std::string &_practice_name,
const AppSecPracticeSpec &parsed_appsec_spec,
const LogTriggerSection &parsed_log_trigger,
const std::string &default_mode,
const AppSecTrustedSources &parsed_trusted_sources)
:
application_urls(_application_urls),
asset_id(_asset_id),
asset_name(_asset_name),
rule_id(_rule_id),
rule_name(_rule_name),
practice_id(_practice_id),
practice_name(_practice_name),
context("practiceId(" + practice_id +")"),
web_attack_mitigation_severity(parsed_appsec_spec.getWebAttacks().getMinimumConfidence()),
web_attack_mitigation_mode(parsed_appsec_spec.getWebAttacks().getMode(default_mode)),
practice_advanced_config(parsed_appsec_spec),
anti_bots(parsed_appsec_spec.getAntiBot()),
trusted_sources({parsed_trusted_sources})
{
web_attack_mitigation = true;
web_attack_mitigation_action =
web_attack_mitigation_severity == "critical" ? "low" :
web_attack_mitigation_severity == "high" ? "balanced" :
web_attack_mitigation_severity == "medium" ? "high" :
"Error";
triggers.push_back(TriggersInWaapSection(parsed_log_trigger));
for (const SourcesIdentifiers &source_ident : parsed_trusted_sources.getSourcesIdentifiers()) {
overrides.push_back(AppSecOverride(source_ident));
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
std::string disabled_str = "Disabled";
std::string detect_str = "Detect";
std::vector<std::string> empty_list;
out_ar(
cereal::make_nvp("context", context),
cereal::make_nvp("webAttackMitigation", web_attack_mitigation),
cereal::make_nvp("webAttackMitigationSeverity", web_attack_mitigation_severity),
cereal::make_nvp("webAttackMitigationAction", web_attack_mitigation_action),
cereal::make_nvp("webAttackMitigationMode", web_attack_mitigation_mode),
cereal::make_nvp("practiceAdvancedConfig", practice_advanced_config),
cereal::make_nvp("csrfProtection", disabled_str),
cereal::make_nvp("openRedirect", disabled_str),
cereal::make_nvp("errorDisclosure", disabled_str),
cereal::make_nvp("practiceId", practice_id),
cereal::make_nvp("practiceName", practice_name),
cereal::make_nvp("assetId", asset_id),
cereal::make_nvp("assetName", asset_name),
cereal::make_nvp("ruleId", rule_id),
cereal::make_nvp("ruleName", rule_name),
cereal::make_nvp("triggers", triggers),
cereal::make_nvp("applicationUrls", application_urls),
cereal::make_nvp("overrides", overrides),
cereal::make_nvp("trustedSources", trusted_sources),
cereal::make_nvp("waapParameters", empty_list),
cereal::make_nvp("botProtection", false),
cereal::make_nvp("antiBot", anti_bots),
cereal::make_nvp("botProtection_v2", detect_str)
);
}
const std::string & getPracticeId() const { return practice_id; }
bool
operator<(const WebAppSection &other) const
{
return getPracticeId() < other.getPracticeId();
}
private:
std::string application_urls;
std::string asset_id;
std::string asset_name;
std::string rule_id;
std::string rule_name;
std::string practice_id;
std::string practice_name;
std::string context;
std::string web_attack_mitigation_action;
std::string web_attack_mitigation_severity;
std::string web_attack_mitigation_mode;
bool web_attack_mitigation;
std::vector<TriggersInWaapSection> triggers;
PracticeAdvancedConfig practice_advanced_config;
AppSecPracticeAntiBot anti_bots;
std::vector<AppSecTrustedSources> trusted_sources;
std::vector<AppSecOverride> overrides;
};
class WebAPISection
{
public:
WebAPISection(
const std::string &_application_urls,
const std::string &_asset_id,
const std::string &_asset_name,
const std::string &_rule_id,
const std::string &_rule_name,
const std::string &_practice_id,
const std::string &_practice_name,
const std::string &_web_attack_mitigation_action,
const std::string &_web_attack_mitigation_severity,
const std::string &_web_attack_mitigation_mode,
bool _web_attack_mitigation,
const AppSecPracticeSpec &parsed_appsec_spec)
:
application_urls(_application_urls),
asset_id(_asset_id),
asset_name(_asset_name),
rule_id(_rule_id),
rule_name(_rule_name),
practice_id(_practice_id),
practice_name(_practice_name),
context("practiceId(" + practice_id +")"),
web_attack_mitigation_action(_web_attack_mitigation_action),
web_attack_mitigation_severity(_web_attack_mitigation_severity),
web_attack_mitigation_mode(_web_attack_mitigation_mode),
web_attack_mitigation(_web_attack_mitigation),
practice_advanced_config(parsed_appsec_spec)
{}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
std::string disabled_str = "Disabled";
std::vector<std::string> empty_list;
out_ar(
cereal::make_nvp("application_urls", application_urls),
cereal::make_nvp("asset_id", asset_id),
cereal::make_nvp("asset_name", asset_name),
cereal::make_nvp("context", context),
cereal::make_nvp("practiceAdvancedConfig", practice_advanced_config),
cereal::make_nvp("practice_id", practice_id),
cereal::make_nvp("practice_name", practice_name),
cereal::make_nvp("ruleId", rule_id),
cereal::make_nvp("ruleName", rule_name),
cereal::make_nvp("schemaValidation", false),
cereal::make_nvp("schemaValidation_v2", disabled_str),
cereal::make_nvp("web_attack_mitigation", web_attack_mitigation),
cereal::make_nvp("web_attack_mitigation_action", web_attack_mitigation_action),
cereal::make_nvp("web_attack_mitigation_severity", web_attack_mitigation_severity),
cereal::make_nvp("web_attack_mitigation_mode", web_attack_mitigation_mode),
cereal::make_nvp("oas", empty_list),
cereal::make_nvp("trustedSources", empty_list),
cereal::make_nvp("triggers", empty_list),
cereal::make_nvp("waapParameters", empty_list),
cereal::make_nvp("overrides", empty_list)
);
}
const std::string & getPracticeId() const { return practice_id; }
private:
std::string application_urls;
std::string asset_id;
std::string asset_name;
std::string rule_id;
std::string rule_name;
std::string practice_id;
std::string practice_name;
std::string context;
std::string web_attack_mitigation_action;
std::string web_attack_mitigation_severity;
std::string web_attack_mitigation_mode;
bool web_attack_mitigation;
PracticeAdvancedConfig practice_advanced_config;
};
class AppSecRulebase
{
public:
AppSecRulebase(
std::vector<WebAppSection> _webApplicationPractices,
std::vector<WebAPISection> _webAPIPractices)
:
webApplicationPractices(_webApplicationPractices),
webAPIPractices(_webAPIPractices) {}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("WebAPISecurity", webAPIPractices),
cereal::make_nvp("WebApplicationSecurity", webApplicationPractices)
);
}
private:
std::vector<WebAppSection> webApplicationPractices;
std::vector<WebAPISection> webAPIPractices;
};
class AppSecWrapper
{
public:
AppSecWrapper(const AppSecRulebase &_app_sec)
:
app_sec_rulebase(_app_sec)
{}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(cereal::make_nvp("WAAP", app_sec_rulebase));
}
private:
AppSecRulebase app_sec_rulebase;
};
class ParsedRule
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec ParsedRule";
parseAppsecJSONKey<std::vector<std::string>>("exceptions", exceptions, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("triggers", log_triggers, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("practices", practices, archive_in);
parseAppsecJSONKey<std::string>("mode", mode, archive_in);
parseAppsecJSONKey<std::string>("custom-response", custom_response, archive_in);
parseAppsecJSONKey<std::string>("source-identifiers", source_identifiers, archive_in);
parseAppsecJSONKey<std::string>("trusted-sources", trusted_sources, archive_in);
try {
archive_in(cereal::make_nvp("host", host));
} catch (const cereal::Exception &e)
{} // The default ParsedRule does not hold a host, so no error handling
}
const std::vector<std::string> & getExceptions() const { return exceptions; }
const std::vector<std::string> & getLogTriggers() const { return log_triggers; }
const std::vector<std::string> & getPractices() const { return practices; }
const std::string & getHost() const { return host; }
const std::string & getMode() const { return mode; }
void setMode(const std::string &_mode) { mode = _mode; };
const std::string & getCustomResponse() const { return custom_response; }
const std::string & getSourceIdentifiers() const { return source_identifiers; }
const std::string & getTrustedSources() const { return trusted_sources; }
private:
std::vector<std::string> exceptions;
std::vector<std::string> log_triggers;
std::vector<std::string> practices;
std::string host;
std::string mode;
std::string custom_response;
std::string source_identifiers;
std::string trusted_sources;
};
std::ostream &
operator<<(std::ostream &os, const ParsedRule &obj)
{
os
<< "host: "
<< obj.getHost()
<< std::endl << "log trigger: "
<< makeSeparatedStr(obj.getLogTriggers(), ",")
<< std::endl << "mode: "
<< obj.getMode()
<< std::endl << "practices: "
<< makeSeparatedStr(obj.getPractices(), ",")
<< std::endl << "web responce: "
<< obj.getCustomResponse()
<< std::endl << " Exceptions: [" << std::endl
<< makeSeparatedStr(obj.getExceptions(), ",")
<< std::endl << "]";
return os;
}
class AppsecPolicySpec : Singleton::Consume<I_Environment>
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec policy spec";
parseAppsecJSONKey<ParsedRule>("default", default_rule, archive_in);
auto default_mode_annot =
Singleton::Consume<I_Environment>::by<AppsecPolicySpec>()->get<std::string>("default mode annotation");
if (default_mode_annot.ok() && !default_mode_annot.unpack().empty() && default_rule.getMode().empty()) {
default_rule.setMode(default_mode_annot.unpack());
}
parseAppsecJSONKey<std::vector<ParsedRule>>("specific-rules", specific_rules, archive_in);
}
const ParsedRule & getDefaultRule() const { return default_rule; }
const std::vector<ParsedRule> & getSpecificRules() const { return specific_rules; }
private:
ParsedRule default_rule;
std::vector<ParsedRule> specific_rules;
};
std::ostream &
operator<<(std::ostream &os, const AppsecPolicySpec &obj)
{
os
<< "Default Rule: "
<< obj.getDefaultRule()
<< std::endl <<"Specific Rules: [" << std::endl
<< makeSeparatedStr(obj.getSpecificRules(), ",")
<< std::endl << "]";
return os;
}
#endif // __APPSEC_PRACTICE_SECTION_H__

View File

@@ -0,0 +1,313 @@
// 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 __EXCEPTPIONS_SECTION_H__
#define __EXCEPTPIONS_SECTION_H__
#include <string>
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
#include "rest.h"
#include "k8s_policy_common.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class AppsecExceptionSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec exception spec";
parseAppsecJSONKey<std::string>("action", action, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("countryCode", country_code, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("countryName", country_name, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("hostName", host_name, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("paramName", param_name, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("paramValue", param_value, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("protectionName", protection_name, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("sourceIdentifier", source_identifier, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("sourceIp", source_ip, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("url", url, archive_in);
}
const std::string & getAction() const { return action; }
const std::vector<std::string> & getCountryCode() const { return country_code; }
const std::vector<std::string> & getCountryName() const { return country_name; }
const std::vector<std::string> & getHostName() const { return host_name; }
const std::vector<std::string> & getParamName() const { return param_name; }
const std::vector<std::string> & getParamValue() const { return param_value; }
const std::vector<std::string> & getProtectionName() const { return protection_name; }
const std::vector<std::string> & getSourceIdentifier() const { return source_identifier; }
const std::vector<std::string> & getSourceIp() const { return source_ip; }
const std::vector<std::string> & getUrl() const { return url; }
private:
std::string action;
std::vector<std::string> country_code;
std::vector<std::string> country_name;
std::vector<std::string> host_name;
std::vector<std::string> param_name;
std::vector<std::string> param_value;
std::vector<std::string> protection_name;
std::vector<std::string> source_identifier;
std::vector<std::string> source_ip;
std::vector<std::string> url;
};
std::ostream &
operator<<(std::ostream &os, const AppsecExceptionSpec &obj)
{
os
<< "action: "
<< makeSeparatedStr(obj.getAction(), ",")
<< "countryCode: "
<< makeSeparatedStr(obj.getCountryCode(), ",")
<< "countryName: "
<< makeSeparatedStr(obj.getCountryName(), ",")
<< "hostName: "
<< makeSeparatedStr(obj.getHostName(), ",")
<< "paramName: "
<< makeSeparatedStr(obj.getParamName(), ",")
<< "paramValue: "
<< makeSeparatedStr(obj.getParamValue(), ",")
<< "protectionName: "
<< makeSeparatedStr(obj.getProtectionName(), ",")
<< "sourceIdentifier: "
<< makeSeparatedStr(obj.getSourceIdentifier(), ",")
<< "sourceIp: "
<< makeSeparatedStr(obj.getSourceIp(), ",")
<< "url: "
<< makeSeparatedStr(obj.getUrl(), ",");
return os;
}
class ExceptionMatch
{
public:
ExceptionMatch(const AppsecExceptionSpec &parsed_exception)
:
match_type(MatchType::Operator),
op("and")
{
if (!parsed_exception.getCountryCode().empty()) {
items.push_back(ExceptionMatch("countryCode", parsed_exception.getCountryCode()));
}
if (!parsed_exception.getCountryName().empty()) {
items.push_back(ExceptionMatch("countryName", parsed_exception.getCountryName()));
}
if (!parsed_exception.getHostName().empty()) {
items.push_back(ExceptionMatch("hostName", parsed_exception.getHostName()));
}
if (!parsed_exception.getParamName().empty()) {
items.push_back(ExceptionMatch("paramName", parsed_exception.getParamName()));
}
if (!parsed_exception.getParamValue().empty()) {
items.push_back(ExceptionMatch("paramValue", parsed_exception.getParamValue()));
}
if (!parsed_exception.getProtectionName().empty()) {
items.push_back(ExceptionMatch("protectionName", parsed_exception.getProtectionName()));
}
if (!parsed_exception.getSourceIdentifier().empty()) {
items.push_back(ExceptionMatch("sourceIdentifier", parsed_exception.getSourceIdentifier()));
}
if (!parsed_exception.getSourceIp().empty()) {
items.push_back(ExceptionMatch("sourceIp", parsed_exception.getSourceIp()));
}
if (!parsed_exception.getUrl().empty()) {
items.push_back(ExceptionMatch("url", parsed_exception.getUrl()));
}
}
ExceptionMatch(const std::string &_key, const std::vector<std::string> &_value)
:
match_type(MatchType::Condition),
key(_key),
op("in"),
value(_value)
{}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
switch (match_type) {
case (MatchType::Condition): {
std::string type_str = "condition";
out_ar(
cereal::make_nvp("key", key),
cereal::make_nvp("op", op),
cereal::make_nvp("type", type_str),
cereal::make_nvp("value", value)
);
break;
}
case (MatchType::Operator): {
std::string type_str = "operator";
out_ar(
cereal::make_nvp("op", op),
cereal::make_nvp("type", type_str),
cereal::make_nvp("items", items)
);
break;
}
default: {
dbgError(D_K8S_POLICY) << "No match for exception match type: " << static_cast<int>(match_type);
}
}
}
private:
MatchType match_type;
std::string key;
std::string op;
std::vector<std::string> value;
std::vector<ExceptionMatch> items;
};
class ExceptionBehavior
{
public:
ExceptionBehavior(
const std::string &_key,
const std::string &_value)
:
key(_key),
value(_value)
{
try {
id = to_string(boost::uuids::random_generator()());
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate exception behavior UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("key", key),
cereal::make_nvp("value", value),
cereal::make_nvp("id", id)
);
}
const std::string getBehaviorId() const { return id; }
private:
std::string key;
std::string id;
std::string value;
};
class InnerException
{
public:
InnerException(
ExceptionBehavior _behavior,
ExceptionMatch _match)
:
behavior(_behavior),
match(_match) {}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("behavior", behavior),
cereal::make_nvp("match", match)
);
}
const std::string getBehaviorId() const { return behavior.getBehaviorId(); }
bool
operator<(const InnerException &other) const
{
return getBehaviorId() < other.getBehaviorId();
}
private:
ExceptionBehavior behavior;
ExceptionMatch match;
};
class ExceptionsRulebase
{
public:
ExceptionsRulebase(
std::vector<InnerException> _exceptions)
:
exceptions(_exceptions)
{
std::string context_id_str = "";
for (const InnerException exception : exceptions) {
std::string curr_id = "parameterId(" + exception.getBehaviorId() + "), ";
context_id_str += curr_id;
}
context_id_str = context_id_str.substr(0, context_id_str.size() - 2);
context = "Any(" + context_id_str + ")";
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("context", context),
cereal::make_nvp("exceptions", exceptions)
);
}
private:
std::string context;
std::vector<InnerException> exceptions;
};
class ExceptionsWrapper
{
public:
class Exception
{
public:
Exception(const std::vector<ExceptionsRulebase> &_exception) : exception(_exception) {}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(cereal::make_nvp("exception", exception));
}
private:
std::vector<ExceptionsRulebase> exception;
};
ExceptionsWrapper(const std::vector<ExceptionsRulebase> &_exception) : exception_rulebase(Exception(_exception))
{}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("rulebase", exception_rulebase)
);
}
private:
Exception exception_rulebase;
};
#endif // __EXCEPTPIONS_SECTION_H__

View File

@@ -0,0 +1,224 @@
// 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 __INGRESS_DATA_H__
#define __INGRESS_DATA_H__
#include <vector>
#include <map>
#include "config.h"
#include "debug.h"
#include "rest.h"
#include "cereal/archives/json.hpp"
USE_DEBUG_FLAG(D_K8S_POLICY);
class IngressMetadata
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "IngressMetadata load";
parseAppsecJSONKey<std::string>("name", name, archive_in);
parseAppsecJSONKey<std::string>("resourceVersion", resourceVersion, archive_in);
parseAppsecJSONKey<std::string>("namespace", namespace_name, archive_in);
parseAppsecJSONKey<std::map<std::string, std::string>>("annotations", annotations, archive_in);
}
const std::string & getName() const { return name; }
const std::string & getResourceVersion() const { return resourceVersion; }
const std::string & getNamespace() const { return namespace_name; }
const std::map<std::string, std::string> & getAnnotations() const { return annotations; }
private:
std::string name;
std::string resourceVersion;
std::string namespace_name;
std::map<std::string, std::string> annotations;
};
class IngressRulePath
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading ingress defined rule path";
parseAppsecJSONKey<std::string>("path", path, archive_in);
}
const std::string & getPath() const { return path; }
private:
std::string path;
};
std::ostream &
operator<<(std::ostream &os, const IngressRulePath &obj)
{
os << obj.getPath();
return os;
}
class IngressRulePathsWrapper
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading ingress defined rule path wrapper";
parseAppsecJSONKey<std::vector<IngressRulePath>>("paths", paths, archive_in);
}
const std::vector<IngressRulePath> & getRulePaths() const { return paths; }
private:
std::vector<IngressRulePath> paths;
};
class IngressDefinedRule
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading ingress defined rule";
parseAppsecJSONKey<std::string>("host", host, archive_in);
parseAppsecJSONKey<IngressRulePathsWrapper>("http", paths_wrapper, archive_in);
}
const std::string & getHost() const { return host; }
const IngressRulePathsWrapper & getPathsWrapper() const { return paths_wrapper; }
private:
std::string host;
IngressRulePathsWrapper paths_wrapper;
};
std::ostream &
operator<<(std::ostream &os, const IngressDefinedRule &obj)
{
os
<< "host: "
<< obj.getHost()
<< ", paths: [" << std::endl
<< makeSeparatedStr(obj.getPathsWrapper().getRulePaths(), ",")
<< std::endl << "]";
return os;
}
class DefaultBackend
{
public:
void
load(cereal::JSONInputArchive &)
{
dbgTrace(D_K8S_POLICY) << "Loading Default Backend";
is_exists = true;
}
bool isExists() const { return is_exists; }
private:
bool is_exists = false;
};
class IngressSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading single ingress spec";
parseAppsecJSONKey<std::string>("ingressClassName", ingress_class_name, archive_in);
parseAppsecJSONKey<std::vector<IngressDefinedRule>>("rules", rules, archive_in);
parseAppsecJSONKey<DefaultBackend>("defaultBackend", default_backend, archive_in);
}
const std::string & getIngressClassName() const { return ingress_class_name; }
const std::vector<IngressDefinedRule> & getRules() const { return rules; }
bool isDefaultBackendExists() const { return default_backend.isExists(); }
private:
std::string ingress_class_name;
std::vector<IngressDefinedRule> rules;
DefaultBackend default_backend;
};
std::ostream &
operator<<(std::ostream &os, const IngressSpec &obj)
{
os
<< "Ingress Spec - ingressClassName: "
<< obj.getIngressClassName()
<< ", rules: [" << std::endl
<< makeSeparatedStr(obj.getRules(), ",")
<< std::endl << "]";
return os;
}
class SingleIngressData
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading single ingress data";
parseAppsecJSONKey<IngressMetadata>("metadata", metadata, archive_in);
parseAppsecJSONKey<IngressSpec>("spec", spec, archive_in);
}
const IngressMetadata & getMetadata() const { return metadata; }
const IngressSpec & getSpec() const { return spec; }
private:
IngressMetadata metadata;
IngressSpec spec;
};
class IngressData : public ClientRest
{
public:
bool
loadJson(const std::string &json)
{
std::string modified_json = json;
modified_json.pop_back();
std::stringstream in;
in.str(modified_json);
dbgTrace(D_K8S_POLICY) << "Loading ingress data";
try {
cereal::JSONInputArchive in_ar(in);
in_ar(
cereal::make_nvp("apiVersion", apiVersion),
cereal::make_nvp("items", items)
);
} catch (cereal::Exception &e) {
dbgError(D_K8S_POLICY) << "Failed to load ingress data JSON. Error: " << e.what();
return false;
}
return true;
}
const std::string & getapiVersion() const { return apiVersion; }
const std::vector<SingleIngressData> & getItems() const { return items; }
private:
std::string apiVersion;
std::vector<SingleIngressData> items;
};
#endif // __INGRESS_DATA_H__

View File

@@ -0,0 +1,103 @@
// 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 __K8S_POLICY_COMMON_H__
#define __K8S_POLICY_COMMON_H__
#include <map>
#include <set>
#include <string>
#include <cereal/archives/json.hpp>
#include "config.h"
#include "debug.h"
#include "rest.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
enum class PracticeType { WebApplication, WebAPI };
enum class TriggerType { Log, WebUserResponse };
enum class MatchType { Condition, Operator };
static const std::unordered_map<std::string, MatchType> string_to_match_type = {
{ "condition", MatchType::Condition },
{ "operator", MatchType::Operator }
};
static const std::unordered_map<std::string, PracticeType> string_to_practice_type = {
{ "WebApplication", PracticeType::WebApplication },
{ "WebAPI", PracticeType::WebAPI }
};
static const std::unordered_map<std::string, TriggerType> string_to_trigger_type = {
{ "log", TriggerType::Log },
{ "WebUserResponse", TriggerType::WebUserResponse }
};
static const std::unordered_map<std::string, std::string> key_to_practices_val = {
{ "prevent-learn", "Prevent"},
{ "detect-learn", "Detect"},
{ "prevent", "Prevent"},
{ "detect", "Detect"},
{ "inactive", "Inactive"}
};
template <typename T>
void
parseAppsecJSONKey(
const std::string &key_name,
T &value,
cereal::JSONInputArchive &archive_in,
const T &default_value = T())
{
try {
archive_in(cereal::make_nvp(key_name, value));
} catch (const cereal::Exception &e) {
archive_in.setNextName(nullptr);
value = default_value;
dbgDebug(D_K8S_POLICY)
<< "Could not parse the required key. Key: "
<< key_name
<< ", Error: "
<< e.what();
}
}
template <typename T>
class AppsecSpecParser : public ClientRest
{
public:
bool
loadJson(const std::string &json)
{
std::string modified_json = json;
modified_json.pop_back();
std::stringstream ss;
ss.str(modified_json);
try {
cereal::JSONInputArchive in_ar(ss);
in_ar(cereal::make_nvp("spec", spec));
} catch (cereal::Exception &e) {
dbgError(D_K8S_POLICY) << "Failed to load spec JSON. Error: " << e.what();
return false;
}
return true;
}
const T & getSpec() const { return spec; }
private:
T spec;
};
#endif // __K8S_POLICY_COMMON_H__

View File

@@ -0,0 +1,391 @@
// 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 __RULES_CONFIG_SECTION_H__
#define __RULES_CONFIG_SECTION_H__
#include <string>
#include <algorithm>
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
#include "k8s_policy_common.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class AssetUrlParser
{
public:
std::string query_string, asset_uri, protocol, asset_url, port;
AssetUrlParser()
{}
AssetUrlParser(const std::string &asset)
{
parse(asset);
}
private:
static AssetUrlParser
parse(const std::string &uri)
{
AssetUrlParser result;
using iterator_t = std::string::const_iterator;
if (uri.length() == 0) return result;
iterator_t uri_end = uri.end();
// get query start
iterator_t query_start = std::find(uri.begin(), uri_end, '?');
// protocol
iterator_t protocol_start = uri.begin();
iterator_t protocol_end = std::find(protocol_start, uri_end, ':'); //"://");
if (protocol_end != uri_end) {
std::string http_protocol = &*(protocol_end);
if ((http_protocol.length() > 3) && (http_protocol.substr(0, 3) == "://")) {
result.protocol = std::string(protocol_start, protocol_end);
protocol_end += 3; // ://
} else {
protocol_end = uri.begin(); // no protocol
}
} else {
protocol_end = uri.begin(); // no protocol
}
// URL
iterator_t host_start = protocol_end;
iterator_t path_start = std::find(host_start, uri_end, '/');
iterator_t host_end = std::find(protocol_end, (path_start != uri_end) ? path_start : query_start, ':');
result.asset_url = std::string(host_start, host_end);
// port
if ((host_end != uri_end) && ((&*(host_end))[0] == ':')) { // we have a port
host_end++;
iterator_t portEnd = (path_start != uri_end) ? path_start : query_start;
result.port = std::string(host_end, portEnd);
}
// URI
if (path_start != uri_end) result.asset_uri = std::string(path_start, query_start);
// query
if (query_start != uri_end) result.query_string = std::string(query_start, uri.end());
return result;
} // Parse
}; // uri
class PracticeSection
{
public:
PracticeSection(const std::string &_id, const std::string &_type, const std::string &_practice_name)
{
auto maybe_type = string_to_practice_type.find(_type);
if (maybe_type == string_to_practice_type.end()) {
dbgError(D_K8S_POLICY) << "Illegal pracrtice type: " << _type;
return;
}
type = _type;
name = _practice_name;
id = _id;
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("practiceId", id),
cereal::make_nvp("practiceName", name),
cereal::make_nvp("practiceType", type)
);
}
const std::string & getPracticeId() const { return id; }
const std::string & getPracticeName() const { return name; }
private:
std::string id;
std::string name;
std::string type;
};
class ParametersSection
{
public:
ParametersSection(
const std::string &_id,
const std::string &_name)
:
name(_name),
id(_id)
{
if (_id.empty() && _name.empty()) {
dbgError(D_K8S_POLICY) << "Illegal Parameter values. Name and ID are empty";
return;
}
}
const std::string & getId() const { return id; }
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("parameterId", id),
cereal::make_nvp("parameterName", name),
cereal::make_nvp("parameterType", type)
);
}
private:
std::string name;
std::string id;
std::string type = "Exception";
};
class RulesTriggerSection
{
public:
RulesTriggerSection(
const std::string &_name,
const std::string &_id,
const std::string &_type)
:
name(_name),
id(_id)
{
if (_name.empty() && _id.empty()) {
dbgError(D_K8S_POLICY) << "Illegal values for trigger. Name and ID are empty";
return;
}
auto maybe_type = string_to_trigger_type.find(_type);
if (maybe_type == string_to_trigger_type.end()) {
dbgError(D_K8S_POLICY) << "Illegal trigger type in rule: " << _type;
return;
}
type = _type;
}
const std::string & getId() const { return id; }
const std::string & getName() const { return id; }
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("triggerId", id),
cereal::make_nvp("triggerName", name),
cereal::make_nvp("triggerType", type)
);
}
private:
std::string name;
std::string id;
std::string type;
};
class RulesConfigRulebase
{
public:
RulesConfigRulebase()
{}
RulesConfigRulebase(
const std::string &_name,
const std::string &_url,
const std::string &_uri,
std::vector<PracticeSection> _practices,
std::vector<ParametersSection> _parameters,
std::vector<RulesTriggerSection> _triggers)
:
name(_name),
practices(_practices),
parameters(_parameters),
triggers(_triggers)
{
try {
id = to_string(boost::uuids::random_generator()());
bool any = _name == "Any" && _url == "Any" && _uri == "Any";
if (_uri != "/") {
context = any ? "All()" : "Any("
"All("
"Any("
"EqualHost(" + _url + ")"
"),"
"EqualListeningPort(80)" +
std::string(_uri.empty() ? "" : ",BeginWithUri(" + _uri + ")") +
"),"
"All("
"Any("
"EqualHost(" + _url + ")"
"),"
"EqualListeningPort(443)" +
std::string(_uri.empty() ? "" : ",BeginWithUri(" + _uri + ")") +
")"
")";
} else {
context = any ? "All()" : "Any("
"All("
"Any("
"EqualHost(" + _url + ")"
"),"
"EqualListeningPort(80)"
"),"
"All("
"Any("
"EqualHost(" + _url + ")"
"),"
"EqualListeningPort(443)"
")"
")";
}
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate rule UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
std::string empty_str = "";
out_ar(
cereal::make_nvp("assetId", id),
cereal::make_nvp("assetName", name),
cereal::make_nvp("ruleId", id),
cereal::make_nvp("ruleName", name),
cereal::make_nvp("context", context),
cereal::make_nvp("priority", 1),
cereal::make_nvp("isCleanup", false),
cereal::make_nvp("parameters", parameters),
cereal::make_nvp("practices", practices),
cereal::make_nvp("triggers", triggers),
cereal::make_nvp("zoneId", empty_str),
cereal::make_nvp("zoneName", empty_str)
);
}
const std::string & getRuleId() const { return id; }
const std::string & getAssetName() const { return name; }
const std::string & getRuleName() const { return name; }
const std::string & getAsstId() const { return id; }
const std::string & getPracticeId() const { return practices[0].getPracticeId(); }
const std::string & getPracticeName() const { return practices[0].getPracticeName(); }
const std::vector<PracticeSection> & getPractice() const { return practices; }
const std::vector<ParametersSection> & getParameters() const { return parameters; }
const std::vector<RulesTriggerSection> & getTriggers() const { return triggers; }
private:
std::string context;
std::string id;
std::string name;
std::vector<PracticeSection> practices;
std::vector<ParametersSection> parameters;
std::vector<RulesTriggerSection> triggers;
};
class RulesConfigWrapper
{
public:
class RulesConfig
{
public:
RulesConfig(const std::vector<RulesConfigRulebase> &_rules_config)
:
rules_config(_rules_config)
{
sort(rules_config.begin(), rules_config.end(), sortBySpecific);
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("rulesConfig", rules_config)
);
}
private:
static bool
sortBySpecific(const RulesConfigRulebase &first, const RulesConfigRulebase &second)
{
return sortBySpecificAux(first.getAssetName(), second.getAssetName());
}
static bool
sortBySpecificAux(const std::string &first, const std::string &second)
{
if (first.empty()) return false;
if (second.empty()) return true;
AssetUrlParser first_parsed = AssetUrlParser(first);
AssetUrlParser second_parsed = AssetUrlParser(second);
// sort by URL
if (first_parsed.asset_url == "*" && second_parsed.asset_url != "*") return false;
if (second_parsed.asset_url == "*" && first_parsed.asset_url != "*") return true;
// sort by port
if (first_parsed.port == "*" && second_parsed.port != "*") return false;
if (second_parsed.port == "*" && first_parsed.port != "*") return true;
// sort by URI
if (first_parsed.asset_uri == "*" && second_parsed.asset_uri != "*") return false;
if (second_parsed.asset_uri == "*" && first_parsed.asset_uri != "*") return true;
if (first_parsed.asset_uri.empty()) return false;
if (second_parsed.asset_uri.empty()) return true;
if (second_parsed.asset_uri.find(first_parsed.asset_uri) != std::string::npos) return false;
if (first_parsed.asset_uri.find(second_parsed.asset_uri) != std::string::npos) return true;
if (first_parsed.asset_url.empty()) return false;
if (second_parsed.asset_url.empty()) return false;
return second < first;
}
std::vector<RulesConfigRulebase> rules_config;
};
RulesConfigWrapper(const std::vector<RulesConfigRulebase> &_rules_config)
:
rules_config_rulebase(RulesConfig(_rules_config))
{}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("rulebase", rules_config_rulebase)
);
}
private:
RulesConfig rules_config_rulebase;
};
#endif // __RULES_CONFIG_SECTION_H__

View File

@@ -0,0 +1,121 @@
// 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 __SETTINGS_SECTION_H__
#define __SETTINGS_SECTION_H__
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
#include "k8s_policy_common.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class AgentSettingsSection
{
public:
AgentSettingsSection(
const std::string &_key,
const std::string &_value)
:
key(_key),
value(_value)
{
try {
id = to_string(boost::uuids::random_generator()());
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate agent setting UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("id", id),
cereal::make_nvp("key", key),
cereal::make_nvp("value", value)
);
}
const std::string & getSettingId() const { return id; }
private:
std::string id;
std::string key;
std::string value;
};
class SettingsRulebase
{
public:
SettingsRulebase(std::vector<AgentSettingsSection> _agentSettings) : agentSettings(_agentSettings) {}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
std::string profile_type = "Kubernetes";
std::string upgrade_mode = "automatic";
out_ar(
cereal::make_nvp("agentSettings", agentSettings),
cereal::make_nvp("agentType", profile_type),
cereal::make_nvp("allowOnlyDefinedApplications", false),
cereal::make_nvp("anyFog", true),
cereal::make_nvp("maxNumberOfAgents", 10),
cereal::make_nvp("upgradeMode", upgrade_mode)
);
}
private:
std::vector<AgentSettingsSection> agentSettings;
};
class SettingsWrapper
{
public:
SettingsWrapper(SettingsRulebase _agent) : agent(_agent)
{
try {
id = to_string(boost::uuids::random_generator()());
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate Settings Wrapper UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("profileType", profileType),
cereal::make_nvp("tokenType", isToken),
cereal::make_nvp("tokenType", tokenType),
cereal::make_nvp("name", name),
cereal::make_nvp("id", id),
cereal::make_nvp("agent", agent)
);
}
private:
std::string profileType = "agent";
bool isToken = true;
std::string tokenType = "sameToken";
std::string id;
std::string name = "Kubernetes Agents";
SettingsRulebase agent;
};
#endif // __SETTINGS_SECTION_H__

View File

@@ -0,0 +1,79 @@
// 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 __SNORT_SECTION_H__
#define __SNORT_SECTION_H__
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class AgentSettingsSection
{
public:
AgentSettingsSection(std::string _key, std::string _value) : key(_key), value(_value)
{
try {
id = to_string(boost::uuids::random_generator()());
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate agent setting UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("id", id),
cereal::make_nvp("key", key),
cereal::make_nvp("value", value)
);
}
private:
std::string id;
std::string key;
std::string value;
};
class IpsSnortSigsRulebase
{
public:
IpsSnortSigsRulebase(std::vector<AgentSettingsSection> _agentSettings) : agentSettings(_agentSettings) {}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
std::string profile_type = "KubernetesProfile";
std::string upgrade_mode = "automatic";
out_ar(
cereal::make_nvp("agentSettings", agentSettings),
cereal::make_nvp("agentType", profile_type),
cereal::make_nvp("allowOnlyDefinedApplications", false),
cereal::make_nvp("anyFog", true),
cereal::make_nvp("maxNumberOfAgents", 10),
cereal::make_nvp("upgradeMode", upgrade_mode)
);
}
private:
std::vector<AgentSettingsSection> agentSettings;
};
#endif // __SNORT_SECTION_H__

View File

@@ -0,0 +1,625 @@
// 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 __TRIGGERS_SECTION_H__
#define __TRIGGERS_SECTION_H__
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
#include "k8s_policy_common.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class LogTriggerSection
{
public:
LogTriggerSection()
{}
LogTriggerSection(
const std::string &_name,
const std::string &_verbosity,
const std::string &_extendloggingMinSeverity,
bool _extendlogging,
bool _logToAgent,
bool _logToCef,
bool _logToCloud,
bool _logToSyslog,
bool _responseBody,
bool _tpDetect,
bool _tpPrevent,
bool _webBody,
bool _webHeaders,
bool _webRequests,
bool _webUrlPath,
bool _webUrlQuery,
int _cefPortNum,
const std::string &_cefIpAddress,
int _syslogPortNum,
const std::string &_syslogIpAddress,
bool _beautify_logs)
:
name(_name),
verbosity(_verbosity),
extendloggingMinSeverity(_extendloggingMinSeverity),
extendlogging(_extendlogging),
logToAgent(_logToAgent),
logToCef(_logToCef),
logToCloud(_logToCloud),
logToSyslog(_logToSyslog),
responseBody(_responseBody),
tpDetect(_tpDetect),
tpPrevent(_tpPrevent),
webBody(_webBody),
webHeaders(_webHeaders),
webRequests(_webRequests),
webUrlPath(_webUrlPath),
webUrlQuery(_webUrlQuery),
cefPortNum (_cefPortNum),
cefIpAddress (_cefIpAddress),
syslogPortNum (_syslogPortNum),
syslogIpAddress (_syslogIpAddress),
beautify_logs(_beautify_logs)
{
try {
id = to_string(boost::uuids::random_generator()());
context = "triggerId(" + id + ")";
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate log trigger UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
std::string trigger_type = "log";
std::string urlForSyslog = syslogIpAddress + ":" + std::to_string(syslogPortNum);
std::string urlForCef = cefIpAddress + ":" + std::to_string(cefPortNum);
out_ar(
cereal::make_nvp("context", context),
cereal::make_nvp("triggerName", name),
cereal::make_nvp("triggerType", trigger_type),
cereal::make_nvp("verbosity", verbosity),
cereal::make_nvp("acAllow", false),
cereal::make_nvp("acDrop", false),
cereal::make_nvp("complianceViolations", false),
cereal::make_nvp("complianceWarnings", false),
cereal::make_nvp("extendloggingMinSeverity", extendloggingMinSeverity),
cereal::make_nvp("extendlogging", extendlogging),
cereal::make_nvp("logToAgent", logToAgent),
cereal::make_nvp("logToCef", logToCef),
cereal::make_nvp("logToCloud", logToCloud),
cereal::make_nvp("logToSyslog", logToSyslog),
cereal::make_nvp("responseBody", responseBody),
cereal::make_nvp("responseCode", false),
cereal::make_nvp("tpDetect", tpDetect),
cereal::make_nvp("tpPrevent", tpPrevent),
cereal::make_nvp("webBody", webBody),
cereal::make_nvp("webHeaders", webHeaders),
cereal::make_nvp("webRequests", webRequests),
cereal::make_nvp("webUrlPath", webUrlPath),
cereal::make_nvp("webUrlQuery", webUrlQuery),
cereal::make_nvp("urlForSyslog", urlForSyslog),
cereal::make_nvp("urlForCef", urlForCef),
cereal::make_nvp("formatLoggingOutput", beautify_logs)
);
}
const std::string & getTriggerId() const { return id; }
const std::string & getTriggerName() const { return name; }
private:
std::string id;
std::string name;
std::string context;
std::string verbosity;
std::string extendloggingMinSeverity;
bool extendlogging;
bool logToAgent;
bool logToCef;
bool logToCloud;
bool logToSyslog;
bool responseBody;
bool tpDetect;
bool tpPrevent;
bool webBody;
bool webHeaders;
bool webRequests;
bool webUrlPath;
bool webUrlQuery;
int cefPortNum;
std::string cefIpAddress;
int syslogPortNum;
std::string syslogIpAddress;
bool beautify_logs;
};
class WebUserResponseTriggerSection
{
public:
WebUserResponseTriggerSection(
const std::string &_name,
const std::string &_details_level,
const std::string &_response_body,
int _response_code,
const std::string &_response_title)
:
name(_name),
context(),
details_level(_details_level),
response_body(_response_body),
response_title(_response_title),
response_code(_response_code)
{
try {
id = to_string(boost::uuids::random_generator()());
context = "triggerId(" + id + ")";
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate webUserResponse trigger UUID. Error: " << e.what();
}
}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("context", context),
cereal::make_nvp("triggerName", name),
cereal::make_nvp("details level", details_level),
cereal::make_nvp("response body", response_body),
cereal::make_nvp("response code", response_code),
cereal::make_nvp("response title", response_title)
);
}
const std::string & getTriggerId() const { return id; }
const std::string & getTriggerName() const { return name; }
private:
std::string id;
std::string name;
std::string context;
std::string details_level;
std::string response_body;
std::string response_title;
int response_code;
};
class AppSecWebUserResponseSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec web user response spec";
parseAppsecJSONKey<int>("http-response-code", httpResponseCode, archive_in, 403);
parseAppsecJSONKey<std::string>("mode", mode, archive_in, "block-page");
if (mode == "block-page") {
parseAppsecJSONKey<std::string>(
"message-body",
messageBody,
archive_in,
"Openappsec's <b>Application Security</b> has detected an attack and blocked it."
);
parseAppsecJSONKey<std::string>(
"message-title",
messageTitle,
archive_in,
"Attack blocked by web application protection"
);
}
}
int getHttpResponseCode() const { return httpResponseCode; }
const std::string & getMessageBody() const { return messageBody; }
const std::string & getMessageTitle() const { return messageTitle; }
const std::string & getMode() const { return mode; }
private:
int httpResponseCode;
std::string messageBody;
std::string messageTitle;
std::string mode;
};
std::ostream &
operator<<(std::ostream &os, const AppSecWebUserResponseSpec &obj)
{
os
<< "mode: "
<< obj.getMode()
<< "," << std::endl << "message-title: "
<< obj.getMessageTitle()
<< "," << std::endl << "message-body: "
<< obj.getMessageBody()
<< "," << std::endl << "http-response-code: "
<< obj.getHttpResponseCode();
return os;
}
class TriggersRulebase
{
public:
TriggersRulebase(
std::vector<LogTriggerSection> _logTriggers,
std::vector<WebUserResponseTriggerSection> _webUserResponseTriggers)
:
logTriggers(_logTriggers),
webUserResponseTriggers(_webUserResponseTriggers) {}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("log", logTriggers),
cereal::make_nvp("webUserResponse", webUserResponseTriggers)
);
}
private:
std::vector<LogTriggerSection> logTriggers;
std::vector<WebUserResponseTriggerSection> webUserResponseTriggers;
};
class AppsecTriggerAccessControlLogging
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Trigger - Access Control Logging";
parseAppsecJSONKey<bool>("allow-events", allow_events, archive_in, false);
parseAppsecJSONKey<bool>("drop-events", drop_events, archive_in, false);
}
bool isAllowEvents() const { return allow_events; }
bool isDropEvents() const { return drop_events; }
private:
bool allow_events = false;
bool drop_events = false;
};
std::ostream &
operator<<(std::ostream &os, const AppsecTriggerAccessControlLogging &obj)
{
os
<< "AppSec Trigger - Access Control Logging: "
<< "isAllowEvents: "
<< obj.isAllowEvents()
<< " , isDropEvents: "
<< obj.isDropEvents();
return os;
}
class AppsecTriggerAdditionalSuspiciousEventsLogging : public ClientRest
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Trigger - Additional Suspicious Events Logging";
parseAppsecJSONKey<bool>("enabled", enabled, archive_in, true);
parseAppsecJSONKey<bool>("response-body", response_body, archive_in, false);
parseAppsecJSONKey<std::string>("minimum-severity", minimum_severity, archive_in, "high");
}
bool isEnabled() const { return enabled; }
bool isResponseBody() const { return response_body; }
const std::string & getMinimumSeverity() const { return minimum_severity; }
private:
bool enabled = true;
bool response_body = false;
std::string minimum_severity = "high";
};
std::ostream &
operator<<(std::ostream &os, const AppsecTriggerAdditionalSuspiciousEventsLogging &obj)
{
os
<< "AppsecTriggerAdditionalSuspiciousEventsLogging: "
<< "Enabled: "
<< obj.isEnabled()
<< " response_body: "
<< obj.isResponseBody()
<< " minimum_severity: "
<< obj.getMinimumSeverity();
return os;
}
class AppsecTriggerLogging : public ClientRest
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Trigger Logging";
parseAppsecJSONKey<bool>("all-web-requests", all_web_requests, archive_in, false);
parseAppsecJSONKey<bool>("detect-events", detect_events, archive_in, false);
parseAppsecJSONKey<bool>("prevent-events", prevent_events, archive_in, true);
}
bool isAllWebRequests() const { return all_web_requests; }
bool isDetectEvents() const { return detect_events; }
bool isPreventEvents() const { return prevent_events; }
private:
bool all_web_requests = false;
bool detect_events = false;
bool prevent_events = true;
};
std::ostream &
operator<<(std::ostream &os, const AppsecTriggerLogging &obj)
{
os
<< "AppsecTriggerLogging: "
<< "all_web_requests: "
<< obj.isAllWebRequests()
<< ", detect_events: "
<< obj.isDetectEvents()
<< ", prevent_events: "
<< obj.isPreventEvents();
return os;
}
class AppsecTriggerExtendedLogging : public ClientRest
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec Trigger Extended Logging";
parseAppsecJSONKey<bool>("http-headers", http_headers, archive_in, false);
parseAppsecJSONKey<bool>("request-body", request_body, archive_in, false);
parseAppsecJSONKey<bool>("url-path", url_path, archive_in, false);
parseAppsecJSONKey<bool>("url-query", url_query, archive_in, false);
}
bool isHttpHeaders() const { return http_headers; }
bool isRequestBody() const { return request_body; }
bool isUrlPath() const { return url_path; }
bool isUrlQuery() const { return url_query; }
private:
bool http_headers = false;
bool request_body = false;
bool url_path = false;
bool url_query = false;
};
std::ostream &
operator<<(std::ostream &os, const AppsecTriggerExtendedLogging &obj)
{
os
<< "AppsecTriggerExtendedLogging: "
<< "http_headers: "
<< obj.isHttpHeaders()
<< ", request_body: "
<< obj.isRequestBody()
<< ", url_path: "
<< obj.isUrlPath()
<< ", url_query: "
<< obj.isUrlQuery();
return os;
}
class LoggingService
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
parseAppsecJSONKey<std::string>("address", address, archive_in);
parseAppsecJSONKey<std::string>("proto", proto, archive_in);
parseAppsecJSONKey<int>("port", port, archive_in, 514);
}
const std::string & getAddress() const { return address; }
const std::string & getProto() const { return proto; }
int getPort() const { return port; }
private:
std::string address;
std::string proto;
int port = 514;
};
class StdoutLogging
{
public:
StdoutLogging() : format("json") {}
void
load(cereal::JSONInputArchive &archive_in)
{
parseAppsecJSONKey<std::string>("format", format, archive_in, "json");
}
const std::string & getFormat() const { return format; }
private:
std::string format;
};
class AppsecTriggerLogDestination : public ClientRest
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgError(D_K8S_POLICY) << "AppsecTriggerLogDestination load";
// TBD: support "file"
parseAppsecJSONKey<bool>("cloud", cloud, archive_in, false);
StdoutLogging stdout_log;
parseAppsecJSONKey<StdoutLogging>("stdout", stdout_log, archive_in);
agent_local = !(stdout_log.getFormat().empty());
beautify_logs = stdout_log.getFormat() == "json-formatted";
parseAppsecJSONKey<LoggingService>("syslog-service", syslog_service, archive_in);
parseAppsecJSONKey<LoggingService>("cef-service", cef_service, archive_in);
}
int getCefServerUdpPort() const { return getCefServiceData().getPort(); }
int getSyslogServerUdpPort() const { return getSyslogServiceData().getPort(); }
bool isAgentLocal() const { return agent_local; }
bool shouldBeautifyLogs() const { return beautify_logs; }
bool getCloud() const { return cloud; }
bool isCefNeeded() const { return !getCefServiceData().getAddress().empty(); }
bool isSyslogNeeded() const { return !getSyslogServiceData().getAddress().empty(); }
const std::string & getSyslogServerIpv4Address() const { return getSyslogServiceData().getAddress(); }
const std::string & getCefServerIpv4Address() const { return getCefServiceData().getAddress(); }
private:
const LoggingService & getSyslogServiceData() const { return syslog_service; }
const LoggingService & getCefServiceData() const { return cef_service; }
bool cloud = false;
bool agent_local = true;
bool beautify_logs = true;
LoggingService syslog_service;
LoggingService cef_service;
};
std::ostream &
operator<<(std::ostream &os, const AppsecTriggerLogDestination &obj)
{
os
<< "AppSec Trigger Log Destination:" << std::endl
<< "agent_local: "
<< obj.isAgentLocal()
<< ", beautify_logs: "
<< obj.shouldBeautifyLogs()
<< ", cef_server_udp_port: "
<< obj.getCefServerUdpPort()
<< ", syslog_server_udp_port: "
<< obj.getSyslogServerUdpPort()
<< ", cef_service: "
<< obj.isCefNeeded()
<< ", cloud: "
<< obj.getCloud()
<< ", syslog: "
<< obj.isSyslogNeeded()
<< ", syslog_server_ipv4_address: "
<< obj.getSyslogServerIpv4Address()
<< ", cef_server_ipv4_address: "
<< obj.getCefServerIpv4Address();
return os;
}
class AppsecTriggerSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading AppSec trigger spec";
parseAppsecJSONKey<AppsecTriggerAccessControlLogging>(
"access-control-logging",
access_control_logging,
archive_in
);
parseAppsecJSONKey<AppsecTriggerAdditionalSuspiciousEventsLogging>(
"additional-suspicious-events-logging",
additional_suspicious_events_logging,
archive_in
);
parseAppsecJSONKey<AppsecTriggerLogging>("appsec-logging", appsec_logging, archive_in);
parseAppsecJSONKey<AppsecTriggerExtendedLogging>("extended-logging", extended_logging, archive_in);
parseAppsecJSONKey<AppsecTriggerLogDestination>("log-destination", log_destination, archive_in);
}
const AppsecTriggerAccessControlLogging &
getAppsecTriggerAccessControlLogging() const
{
return access_control_logging;
}
const AppsecTriggerAdditionalSuspiciousEventsLogging &
getAppsecTriggerAdditionalSuspiciousEventsLogging() const
{
return additional_suspicious_events_logging;
}
const AppsecTriggerLogging &
getAppsecTriggerLogging() const
{
return appsec_logging;
}
const AppsecTriggerExtendedLogging &
getAppsecTriggerExtendedLogging() const
{
return extended_logging;
}
const AppsecTriggerLogDestination &
getAppsecTriggerLogDestination() const
{
return log_destination;
}
private:
AppsecTriggerAccessControlLogging access_control_logging;
AppsecTriggerAdditionalSuspiciousEventsLogging additional_suspicious_events_logging;
AppsecTriggerLogging appsec_logging;
AppsecTriggerExtendedLogging extended_logging;
AppsecTriggerLogDestination log_destination;
};
std::ostream &
operator<<(std::ostream &os, const AppsecTriggerSpec &obj)
{
os
<< "AppSec Access Control Logging:" << std::endl
<< obj.getAppsecTriggerAccessControlLogging()
<< std::endl << "AppSec Additional Suspocious Events Logging:" << std::endl
<< obj.getAppsecTriggerAdditionalSuspiciousEventsLogging()
<< std::endl << "AppSec Trigger Logging:" << std::endl
<< obj.getAppsecTriggerLogging()
<< std::endl << "Appsec Trigger Extended Logging:" << std::endl
<< obj.getAppsecTriggerExtendedLogging()
<< std::endl << "AppSec Trigger Log Destination:" << std::endl
<< obj.getAppsecTriggerLogDestination();
return os;
}
class TriggersWrapper
{
public:
TriggersWrapper(const TriggersRulebase &_triggers) : triggers_rulebase(_triggers)
{}
void
serialize(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("rulebase", triggers_rulebase)
);
}
private:
TriggersRulebase triggers_rulebase;
};
#endif // __TRIGGERS_SECTION_H__

View File

@@ -0,0 +1,186 @@
// 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 __TRUSTED_SOURCES_SECTION_H__
#define __TRUSTED_SOURCES_SECTION_H__
#include <string>
#include <cereal/archives/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config.h"
#include "debug.h"
#include "k8s_policy_common.h"
USE_DEBUG_FLAG(D_K8S_POLICY);
class TrustedSourcesSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading trusted sources spec";
parseAppsecJSONKey<int>("minNumOfSources", min_num_of_sources, archive_in, 3);
parseAppsecJSONKey<std::vector<std::string>>("sourcesIdentifiers", sources_identifiers, archive_in);
}
int
getMinNumOfSources() const
{
return min_num_of_sources;
}
const std::vector<std::string> &
getSourcesIdentifiers() const
{
return sources_identifiers;
}
private:
int min_num_of_sources;
std::vector<std::string> sources_identifiers;
};
std::ostream &
operator<<(std::ostream &os, const TrustedSourcesSpec &obj)
{
os
<< "Min number of sources: "
<< obj.getMinNumOfSources()
<< ", SourceIdentifiers: ["
<< makeSeparatedStr(obj.getSourcesIdentifiers(), ",")
<< "]";
return os;
}
class SourcesIdentifiers
{
public:
SourcesIdentifiers(const std::string &_source_identifier, const std::string &_value)
:
source_identifier(_source_identifier),
value(_value)
{}
void
save(cereal::JSONOutputArchive &out_ar) const
{
out_ar(
cereal::make_nvp("sourceIdentifier", source_identifier),
cereal::make_nvp("value", value)
);
}
const std::string &
getSourceIdent() const
{
return source_identifier;
}
private:
std::string source_identifier;
std::string value;
};
class SourceIdentifierSpec
{
public:
void
load(cereal::JSONInputArchive &archive_in)
{
dbgTrace(D_K8S_POLICY) << "Loading trusted sources spec";
parseAppsecJSONKey<std::string>("sourceIdentifier", source_identifier, archive_in);
parseAppsecJSONKey<std::vector<std::string>>("value", value, archive_in);
}
const std::string &
getSourceIdentifier() const
{
return source_identifier;
}
const std::vector<std::string> &
getValues() const
{
return value;
}
private:
std::string source_identifier;
std::vector<std::string> value;
};
std::ostream &
operator<<(std::ostream &os, const SourceIdentifierSpec &obj)
{
os
<< "sourceIdentifier: "
<< obj.getSourceIdentifier()
<< ", values: ["
<< makeSeparatedStr(obj.getValues(), ",")
<< "]";
return os;
}
class AppSecTrustedSources
{
public:
AppSecTrustedSources()
{}
AppSecTrustedSources(
const std::string &_name,
int _num_of_sources,
const std::vector<SourcesIdentifiers> &_sources_identifiers)
:
name(_name),
num_of_sources(_num_of_sources),
sources_identifiers(_sources_identifiers)
{
try {
id = to_string(boost::uuids::random_generator()());
} catch (const boost::uuids::entropy_error &e) {
dbgWarning(D_K8S_POLICY) << "Failed to generate Trusted Sources ID. Error: " << e.what();
}
}
void
save(cereal::JSONOutputArchive &out_ar) const
{
std::string parameter_type = "TrustedSource";
out_ar(
cereal::make_nvp("id", id),
cereal::make_nvp("name", name),
cereal::make_nvp("numOfSources", num_of_sources),
cereal::make_nvp("sourcesIdentifiers", sources_identifiers),
cereal::make_nvp("parameterType", parameter_type)
);
}
const std::vector<SourcesIdentifiers> &
getSourcesIdentifiers() const
{
return sources_identifiers;
}
private:
std::string id;
std::string name;
int num_of_sources;
std::vector<SourcesIdentifiers> sources_identifiers;
};
#endif // __TRUSTED_SOURCES_SECTION_H__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
add_library(manifest_controller manifest_controller.cc manifest_diff_calculator.cc manifest_handler.cc)
add_subdirectory(manifest_controller_ut)

View File

@@ -0,0 +1,445 @@
// 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 "manifest_controller.h"
#include "config.h"
#include "debug.h"
#include "sasal.h"
#include "environment.h"
#include "version.h"
#include "log_generator.h"
#include "orchestration_comp.h"
using namespace std;
using namespace ReportIS;
SASAL_START // Orchestration - Manifest Handler
USE_DEBUG_FLAG(D_ORCHESTRATOR);
class IgnoredPackages
{
public:
void
load(istream &input, char delim)
{
string ignored_package;
while (getline(input, ignored_package, delim))
{
if (ignored_package == "all") {
ignore_packages.clear();
ignore_packages.insert(ignored_package);
dbgInfo(D_ORCHESTRATOR) << "Will ignore updates for all packages";
break;
} else if (ignored_package == "none") {
ignore_packages.clear();
dbgInfo(D_ORCHESTRATOR) << "Will not ignore updates of any packages";
break;
}
if (ignored_package.size() > 0) {
ignore_packages.insert(ignored_package);
dbgInfo(D_ORCHESTRATOR) << "Updates for package " << ignored_package << " will be ignored";
}
}
}
void
load(const string &raw_value)
{
string token;
istringstream tokenStream(raw_value);
load(tokenStream, ',');
}
const set<string> & operator*() const { return ignore_packages; }
private:
set<string> ignore_packages;
};
class ManifestController::Impl : Singleton::Provide<I_ManifestController>::From<ManifestController>
{
public:
void init();
bool updateManifest(const string &new_manifest_file) override;
bool loadAfterSelfUpdate() override;
private:
bool changeManifestFile(const string &new_manifest_file);
bool
handlePackage(
const Package &updated_package,
map<string, Package> &current_packages,
const map<string, Package> &new_packages,
map<string, Package> &corrupted_packages
);
ManifestDiffCalculator manifest_diff_calc;
ManifestHandler manifest_handler;
string manifest_file_path;
string corrupted_file_list;
string temp_ext;
string backup_ext;
string packages_dir;
string orch_service_name;
set<string> ignore_packages;
};
void
ManifestController::Impl::init()
{
manifest_diff_calc.init();
manifest_handler.init();
dbgTrace(D_ORCHESTRATOR) << "Manifest controller, file system path prefix: " << getFilesystemPathConfig();
manifest_file_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/conf/manifest.json",
"orchestration",
"Manifest file path"
);
corrupted_file_list = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/conf/corrupted_packages.json",
"orchestration",
"Manifest corrupted files path"
);
temp_ext = getConfigurationWithDefault<string>("_temp", "orchestration", "Temp file extension");
backup_ext = getConfigurationWithDefault<string>(".bk", "orchestration", "Backup file extension");
packages_dir = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/packages",
"orchestration",
"Packages directory"
);
orch_service_name = getConfigurationWithDefault<string>("orchestration", "orchestration", "Service name");
auto ignore_packages_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/conf/ignore-packages.txt",
"orchestration",
"Ignore packages list file path"
);
if (Singleton::Consume<I_OrchestrationTools>::by<ManifestController>()->doesFileExist(ignore_packages_path)) {
try {
ifstream input_stream(ignore_packages_path);
if (!input_stream) {
dbgWarning(D_ORCHESTRATOR)
<< "Cannot open the file with ignored packages. "
<< "File: " << ignore_packages_path;
} else {
IgnoredPackages packages_to_ignore;
packages_to_ignore.load(input_stream, '\n');
ignore_packages = *packages_to_ignore;
input_stream.close();
}
} catch (ifstream::failure &f) {
dbgWarning(D_ORCHESTRATOR)
<< "Cannot read the file with ignored packages."
<< " File: " << ignore_packages_path
<< " Error: " << f.what();
}
}
}
bool
ManifestController::Impl::updateManifest(const string &new_manifest_file)
{
auto i_env = Singleton::Consume<I_Environment>::by<ManifestController>();
auto span_scope = i_env->startNewSpanScope(Span::ContextType::CHILD_OF);
dbgDebug(D_ORCHESTRATOR) << "Starting to update manifest file";
auto ignored_settings_packages = getProfileAgentSetting<IgnoredPackages>("orchestration.IgnoredPackagesList");
set<string> packages_to_ignore = ignore_packages;
if (ignored_settings_packages.ok()) packages_to_ignore = *(*ignored_settings_packages);
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestController>();
if (packages_to_ignore.count("all") > 0) {
dbgTrace(D_ORCHESTRATOR) << "Nothing to update (\"ignore all\" turned on)";
if (!orchestration_tools->copyFile(new_manifest_file, manifest_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to copy a new manifest file";
return false;
}
return true;
}
Maybe<map<string, Package>> parsed_manifest = orchestration_tools->loadPackagesFromJson(new_manifest_file);
if (!parsed_manifest.ok()) {
dbgWarning(D_ORCHESTRATOR) << "Failed to parse the new manifest file. File: " << new_manifest_file;
return false;
}
map<string, Package> new_packages = parsed_manifest.unpack();
map<string, Package> current_packages;
parsed_manifest = orchestration_tools->loadPackagesFromJson(manifest_file_path);
if (!parsed_manifest.ok()){
dbgWarning(D_ORCHESTRATOR) << "Can not parse the current manifest file, start with new one.";
} else {
current_packages = parsed_manifest.unpack();
}
// Remove any update of all ignore packages
for (const auto &ignore_package : packages_to_ignore) {
dbgInfo(D_ORCHESTRATOR) << "Ignoring a package from the manifest. Package name: " << ignore_package;
if (new_packages.count(ignore_package) > 0) {
// Get the change as-is of the ignore package - it won"t update the service
current_packages[ignore_package] = new_packages[ignore_package];
} else {
// Remove the ignore package from the current manifest file - it won't uninstall the service
current_packages.erase(ignore_package);
}
}
map<string, Package> corrupted_packages;
parsed_manifest = orchestration_tools->loadPackagesFromJson(corrupted_file_list);
if (!parsed_manifest.ok()){
dbgWarning(D_ORCHESTRATOR) << "Can not parse corrupted services file, start with new one.";
} else {
corrupted_packages = parsed_manifest.unpack();
}
bool all_cleaned = true;
bool uninstall_done = false;
// Removes all the untracked packages. new_packages will be cleaned from already installed packages
auto packages_to_remove = manifest_diff_calc.filterUntrackedPackages(current_packages, new_packages);
for (auto remove_package = packages_to_remove.begin(); remove_package != packages_to_remove.end();) {
bool uninstall_response = true;
if (remove_package->second.isInstallable().ok()) {
uninstall_response = manifest_handler.uninstallPackage(remove_package->second);
}
if (!uninstall_response) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to uninstall package. Package: " << remove_package->second.getName();
all_cleaned = false;
remove_package++;
} else {
uninstall_done = true;
current_packages.erase(remove_package->first);
remove_package = packages_to_remove.erase(remove_package);
}
}
if (uninstall_done) {
if (!orchestration_tools->packagesToJsonFile(current_packages, manifest_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to update manifest file. File: "
<< manifest_file_path;
} else {
dbgInfo(D_ORCHESTRATOR) << "Manifest file was updated successfully. File: "
<< manifest_file_path;
}
}
bool no_change = new_packages.size() == 0;
// Both new_packages & corrupted_packages will be updated based on updated manifest
bool no_corrupted_package = manifest_diff_calc.filterCorruptedPackages(new_packages, corrupted_packages);
auto orchestration_service = new_packages.find("orchestration");
if (orchestration_service != new_packages.end()) {
// Orchestration needs special handling as manifest should be backup differently
return handlePackage(
orchestration_service->second,
current_packages,
new_packages,
corrupted_packages
);
}
auto wlp_standalone_service = new_packages.find("wlpStandalone");
if (wlp_standalone_service != new_packages.end()) {
// wlpStandalone needs special handling as manifest should be backup differently
return handlePackage(
wlp_standalone_service->second,
current_packages,
new_packages,
corrupted_packages
);
}
bool all_installed = true;
bool any_installed = false;
dbgDebug(D_ORCHESTRATOR) << "Starting to handle " << new_packages.size() <<" new packages";
for (auto &new_package : new_packages) {
if (new_package.second.getType() != Package::PackageType::Service) continue;
size_t prev_size = corrupted_packages.size();
bool handling_response = handlePackage(
new_package.second,
current_packages,
new_packages,
corrupted_packages
);
// During handlePackage function, package installation might fail so it will be added to
// corrupted_packages. Corrupted file needs to be updated accordingly
if (prev_size < corrupted_packages.size() &&
!orchestration_tools->packagesToJsonFile(corrupted_packages, corrupted_file_list)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to update corrupted packages list.";
}
// Orchestration needs special handling as manifest should be backup differently
if (new_package.first.compare(orch_service_name) == 0) {
return handling_response;
}
any_installed = any_installed || handling_response;
all_installed = all_installed && handling_response;
}
bool manifest_file_update = true;
if (all_installed && (any_installed || no_change) && no_corrupted_package) {
manifest_file_update = changeManifestFile(new_manifest_file);
} else if (any_installed) {
manifest_file_update = orchestration_tools->packagesToJsonFile(current_packages, manifest_file_path);
}
return all_installed && manifest_file_update && no_corrupted_package && all_cleaned;
}
// Orchestration package needs a special handling. Old service will die during the upgrade
// so we need to keep temporary manifest file to prevent overwriting. Once Orchestration upgrade
// finish, we return to regular path.
bool
ManifestController::Impl::loadAfterSelfUpdate()
{
dbgDebug(D_ORCHESTRATOR) << "Starting load after the self update function";
string temp_manifest_path = manifest_file_path + temp_ext;
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestController>();
if (!orchestration_tools->doesFileExist(temp_manifest_path)) {
return true;
}
dbgDebug(D_ORCHESTRATOR) << "Orchestration updated itself";
// Run post installation test
auto package_handler = Singleton::Consume<I_PackageHandler>::by<ManifestController>();
string current_file = packages_dir + "/" + orch_service_name + "/" + orch_service_name;
if (!package_handler->postInstallPackage(orch_service_name, current_file + temp_ext)) {
dbgWarning(D_ORCHESTRATOR) << "Failed in post install test. Package: " << orch_service_name;
return false;
}
dbgDebug(D_ORCHESTRATOR) << "Post installation test for the self update package succeed";
if (!changeManifestFile(temp_manifest_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to change manifest file after update the orchestration service.";
return false;
}
dbgDebug(D_ORCHESTRATOR) << "Update the temporary manifest to be the running manifest";
string backup_file = current_file + backup_ext;
string backup_temp_file = backup_file + temp_ext;
if (!package_handler->updateSavedPackage(orch_service_name, current_file + temp_ext)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to update the saved package. Package: " << orch_service_name;
return false;
}
return true;
}
bool
ManifestController::Impl::changeManifestFile(const string &new_manifest_file)
{
dbgDebug(D_ORCHESTRATOR) << "Backup the old manifest file";
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestController>();
if (orchestration_tools->doesFileExist(manifest_file_path)) {
if (!orchestration_tools->copyFile(manifest_file_path,
manifest_file_path + backup_ext)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to backup the old manifest file";
}
}
dbgDebug(D_ORCHESTRATOR) << "Writing new manifest to file";
if (!orchestration_tools->copyFile(new_manifest_file, manifest_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed write new manifest to file";
return false;
}
if (!orchestration_tools->isNonEmptyFile(manifest_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to get manifest file data";
return false;
}
dbgInfo(D_ORCHESTRATOR) << "Manifest file has been updated.";
if (!orchestration_tools->removeFile(new_manifest_file)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to remove new manifest file. Path: " << new_manifest_file;
}
return true;
}
bool
ManifestController::Impl::handlePackage(
const Package &package,
map<string, Package> &current_packages,
const map<string, Package> &new_packages,
map<string, Package> &corrupted_packages)
{
auto i_env = Singleton::Consume<I_Environment>::by<ManifestController>();
auto span_scope = i_env->startNewSpanScope(Span::ContextType::CHILD_OF);
dbgDebug(D_ORCHESTRATOR) << "Handling package. Package: " << package.getName();
if (!package.isInstallable().ok()) {
string report_msg =
"Skipping installation of " + package.getName() + ". Reason: " + package.isInstallable().getErr();
dbgWarning(D_ORCHESTRATOR) << report_msg;
LogGen(report_msg, Audience::SECURITY, Severity::CRITICAL, Priority::HIGH, Tags::ORCHESTRATOR);
current_packages.insert(make_pair(package.getName(), package));
return true;
}
vector<Package> installation_queue;
if (!manifest_diff_calc.buildInstallationQueue(package, installation_queue, current_packages, new_packages)) {
dbgWarning(D_ORCHESTRATOR) << "Failed building installation queue. Package: " << package.getName();
return false;
}
vector<pair<Package, string>> downloaded_files;
if (!manifest_handler.downloadPackages(installation_queue, downloaded_files)) return false;
if (!manifest_handler.installPackages(downloaded_files, current_packages, corrupted_packages)) {
LogGen(
"Failed to install package: " + package.getName(),
Audience::SECURITY,
Severity::CRITICAL,
Priority::HIGH,
Tags::ORCHESTRATOR
);
return false;
}
dbgInfo(D_ORCHESTRATOR) << "Package was installed successfully. Package: " << package.getName();
return true;
}
ManifestController::ManifestController() : Component("ManifestController"), pimpl(make_unique<Impl>()) {}
ManifestController::~ManifestController() {}
void
ManifestController::init()
{
pimpl->init();
}
SASAL_END

View File

@@ -0,0 +1,7 @@
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
manifest_controller_ut
"manifest_controller_ut.cc"
"manifest_controller;logging;orchestration_modules;agent_details;agent_details_reporter;version;config;metric;event_is;-lboost_regex"
)

View File

@@ -0,0 +1,144 @@
// 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 "manifest_diff_calculator.h"
#include "debug.h"
#include "config.h"
#include "sasal.h"
using namespace std;
SASAL_START // Orchestration - Manifest Handler
USE_DEBUG_FLAG(D_ORCHESTRATOR);
void
ManifestDiffCalculator::init()
{
dbgTrace(D_ORCHESTRATOR)
<< "Initializing Manifest diff calculator, file system path prefix:: "
<< getFilesystemPathConfig();
corrupted_file_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/conf/corrupted_packages.json",
"orchestration",
"Manifest corrupted files path"
);
}
// If one of the new packages is already installed, new_packages map is updated accordingly.
// This function return map<string, Package> which contain all packages that should be uninstalled
// based on new manifest
map<string, Package>
ManifestDiffCalculator::filterUntrackedPackages(
const map<string, Package> &current_packages,
map<string, Package> &new_packages)
{
dbgDebug(D_ORCHESTRATOR) << "Starting to scan old packages to remove";
map<string, Package> packages_to_remove;
for (auto current_package = current_packages.begin(); current_package != current_packages.end();) {
auto package = new_packages.find(current_package->first);
if (package == new_packages.end()) {
packages_to_remove.insert(pair<string, Package>(current_package->first, current_package->second));
} else {
if (current_package->second == package->second) {
// if package is already installed, new_packages is updated
new_packages.erase(package);
}
}
current_package++;
}
return packages_to_remove;
}
// If one of the new packages is already known as corrupted, new_packages map is
// updated accordingly.
// Otherwise, corrupted_packages is updated and old corrupted package is deleted.
bool
ManifestDiffCalculator::filterCorruptedPackages(
map<string, Package> &new_packages,
map<string, Package> &corrupted_packages)
{
bool no_corrupted_package_exist = true;
bool any_corrupted_removed = false;
for (auto corrupted_package = corrupted_packages.begin(); corrupted_package != corrupted_packages.end();) {
auto package = new_packages.find(corrupted_package->first);
if (package == new_packages.end()) {
// The corrupted package is not in the new packages list,
// so it should be removed from the corrupted list.
corrupted_package = corrupted_packages.erase(corrupted_package);
any_corrupted_removed = true;
} else {
if (corrupted_package->second == package->second) {
// The corrupted package is still in the new packages list,
// so it should be removed
dbgWarning(D_ORCHESTRATOR) << "Installation package is corrupted."
<< " Package: " << package->second.getName();
new_packages.erase(package);
corrupted_package++;
no_corrupted_package_exist = false;
} else {
// New version of corrupted package was received
corrupted_package = corrupted_packages.erase(corrupted_package);
any_corrupted_removed = true;
}
}
}
if (any_corrupted_removed) {
dbgDebug(D_ORCHESTRATOR) << "Updating corrupted file. File: " << corrupted_file_path;
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestDiffCalculator>();
if (!orchestration_tools->packagesToJsonFile(corrupted_packages, corrupted_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to update corrupted file. Path: " << corrupted_file_path;
return false;
}
}
return no_corrupted_package_exist;
}
// This function build the installation queue recursively and return true if succeeded, false otherwise
// At the beginning, installation_queue is empty and will be filled according package dependences
bool
ManifestDiffCalculator::buildInstallationQueue(
const Package &updated_package,
vector<Package> &installation_queue,
const map<string, Package> &current_packages,
const map<string, Package> &new_packages)
{
vector<string> requires = updated_package.getRequire();
for (size_t i = 0; i < requires.size(); i++) {
auto installed_package = current_packages.find(requires[i]);
auto new_package = new_packages.find(requires[i]);
if (installed_package == current_packages.end() ||
(new_package != new_packages.end() && *installed_package != *new_package)) {
if(!buildInstallationQueue(new_package->second,
installation_queue,
current_packages,
new_packages)) {
return false;
}
} else if (installed_package != current_packages.end()) {
dbgDebug(D_ORCHESTRATOR) << "Package is already installed. Package: " << installed_package->first;
} else if (new_package == new_packages.end()) {
dbgWarning(D_ORCHESTRATOR) << "One of the requested dependencies is corrupted or doesn't exist."
<< " Package: "<< requires[i];
return false;
}
}
installation_queue.push_back(updated_package);
return true;
}
SASAL_END

View File

@@ -0,0 +1,384 @@
// 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 "manifest_handler.h"
#include "debug.h"
#include "config.h"
#include "sasal.h"
#include "agent_details.h"
#include "orchestration_comp.h"
using namespace std;
SASAL_START // Orchestration - Manifest Handler
USE_DEBUG_FLAG(D_ORCHESTRATOR);
void
ManifestHandler::init()
{
dbgTrace(D_ORCHESTRATOR)
<< "Initializing Manifest handler, file system path prefix: "
<< getFilesystemPathConfig();
manifest_file_path = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/conf/manifest.json",
"orchestration",
"Manifest file path"
);
temp_ext = getConfigurationWithDefault<string>("_temp", "orchestration", "Temp file extension");
backup_ext = getConfigurationWithDefault<string>(".bk", "orchestration", "Backup file extension");
packages_dir = getConfigurationWithDefault<string>(
getFilesystemPathConfig() + "/packages", "orchestration",
"Packages directory"
);
orch_service_name = getConfigurationWithDefault<string>("orchestration", "orchestration", "Service name");
default_dir = getConfigurationWithDefault<string>(
getFilesystemPathConfig(),
"orchestration",
"Default Check Point directory"
);
}
Maybe<string>
ManifestHandler::downloadPackage(const Package &package, bool is_clean_installation)
{
Maybe<string> package_download_file = genError("failed to download package, Package: " + package.getName());
Maybe<string> fog_domain = genError("No Fog domain was found");
if (Singleton::exists<I_AgentDetails>()) {
fog_domain = Singleton::Consume<I_AgentDetails>::by<ManifestHandler>()->getFogDomain();
}
if (!is_clean_installation) {
I_MainLoop *i_mainloop = Singleton::Consume<I_MainLoop>::by<ManifestHandler>();
auto pending_time_frame_seconds = getConfigurationWithDefault<int>(
60,
"orchestration",
"Download pending time frame seconds"
);
int pending_time = rand() % pending_time_frame_seconds;
dbgInfo(D_ORCHESTRATOR)
<< "Pending downloading of package "
<< package.getName()
<< " for "
<< pending_time
<< " seconds";
chrono::microseconds pending_time_micro = chrono::seconds(pending_time);
i_mainloop->yield(pending_time_micro);
dbgTrace(D_ORCHESTRATOR) << "Proceeding to package downloading. Package name " << package.getName();
}
auto orchestration_downloader = Singleton::Consume<I_Downloader>::by<ManifestHandler>();
if (!package.getRelativeDownloadPath().empty() && fog_domain.ok()) {
string download_path =
"<JWT>https://" + fog_domain.unpack() + "/download" + package.getRelativeDownloadPath();
package_download_file= orchestration_downloader->downloadFileFromURL(
download_path,
package.getChecksum(),
package.getChecksumType(),
package.getName()
);
}
if (!package_download_file.ok()) {
package_download_file = orchestration_downloader->downloadFileFromURL(
package.getDownloadPath(),
package.getChecksum(),
package.getChecksumType(),
package.getName()
);
}
return package_download_file;
}
bool
ManifestHandler::downloadPackages(
const vector<Package> &packages_to_download,
vector<pair<Package, packageFilePath>> &downloaded_packages)
{
auto i_env = Singleton::Consume<I_Environment>::by<ManifestHandler>();
auto i_orch_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestHandler>();
auto span_scope = i_env->startNewSpanScope(Span::ContextType::CHILD_OF);
for (auto &package : packages_to_download) {
dbgInfo(D_ORCHESTRATOR) << "Downloading package file." << " Package: " << package.getName();
string packages_dir = getConfigurationWithDefault<string>(
"/etc/cp/packages",
"orchestration",
"Packages directory"
);
string current_installation_file = packages_dir + "/" + package.getName() + "/" + package.getName();
bool is_clean_installation = !i_orch_tools->doesFileExist(current_installation_file);
Maybe<string> package_download_file = downloadPackage(package, is_clean_installation);
if (package_download_file.ok()) {
dbgDebug(D_ORCHESTRATOR)
<< "Installation package was downloaded successfully."
<< " Package: " << package.getName();
downloaded_packages.push_back(pair<Package, packageFilePath>(package, package_download_file.unpack()));
} else {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to download installation package. "
<< "Package: " << package.getName()
<< ", Error: " << package_download_file.getErr();
for (auto &package_file : downloaded_packages) {
if (i_orch_tools->removeFile(package_file.second)) {
dbgDebug(D_ORCHESTRATOR) << "Corrupted downloaded package was removed. Package: "
<< package_file.first.getName();
} else {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to removed the download file. Package: "
<< package_file.first.getName()
<< ", Path: "
<< package_file.second;
}
}
downloaded_packages.clear();
string install_error;
if (is_clean_installation) {
string error_hostname_addition = "";
auto maybe_hostname = Singleton::Consume<I_DetailsResolver>::by<ManifestHandler>()->getHostname();
if (maybe_hostname.ok()) {
error_hostname_addition = " on host '" + maybe_hostname.unpack() + "'";
}
install_error =
"Critical Error: Agent/Gateway was not fully deployed" +
error_hostname_addition +
" and is not enforcing a security policy. Retry installation or contact Check Point support.";
} else {
auto agent_details = Singleton::Consume<I_AgentDetails>::by<ManifestHandler>();
install_error =
"Warning: Agent/Gateway '" +
agent_details->getAgentId() +
"' software update failed. Agent is running previous software. Contact Check Point support.";
}
auto orchestration_status = Singleton::Consume<I_OrchestrationStatus>::by<ManifestHandler>();
if (orchestration_status->getManifestError().find("Gateway was not fully deployed") == string::npos) {
orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::MANIFEST,
OrchestrationStatusResult::FAILED,
install_error
);
}
return false;
}
}
return true;
}
bool
ManifestHandler::installPackages(
const vector<pair<Package, packageFilePath>> &downloaded_package_files,
map<packageFilePath, Package> &current_packages,
map<packageFilePath, Package> &corrupted_packages)
{
auto i_env = Singleton::Consume<I_Environment>::by<ManifestHandler>();
auto span_scope = i_env->startNewSpanScope(Span::ContextType::CHILD_OF);
// Patch - reorder packages so that accessControlApp is installed before accessControlKernel
vector<pair<Package, packageFilePath>> patched_downloaded_package_files;
patched_downloaded_package_files.reserve(downloaded_package_files.size());
int ac_kernel_package_idx = -1;
int ac_app_package_idx = -1;
int i = 0;
for (auto &downloaded_package : downloaded_package_files) {
if (downloaded_package.first.getName() == "accessControlApp") {
ac_app_package_idx = i;
} else if (downloaded_package.first.getName() == "accessControlKernel") {
ac_kernel_package_idx = i;
} else {
patched_downloaded_package_files.push_back(downloaded_package);
}
i++;
}
if (ac_app_package_idx != -1) {
patched_downloaded_package_files.push_back(downloaded_package_files.at(ac_app_package_idx));
}
if (ac_kernel_package_idx != -1) {
patched_downloaded_package_files.push_back(downloaded_package_files.at(ac_kernel_package_idx));
}
auto orchestration_status = Singleton::Consume<I_OrchestrationStatus>::by<ManifestHandler>();
for (auto &downloaded_package : patched_downloaded_package_files) {
auto package = downloaded_package.first;
auto package_name = package.getName();
auto package_handler_path = downloaded_package.second;
dbgInfo(D_ORCHESTRATOR) << "Handling package installation. Package: " << package_name;
if (package_name.compare(orch_service_name) == 0) {
orchestration_status->writeStatusToFile();
bool self_update_status = selfUpdate(package, current_packages, package_handler_path);
if (!self_update_status) {
auto agent_details = Singleton::Consume<I_AgentDetails>::by<ManifestHandler>();
string install_error =
"Warning: Agent/Gateway '" +
agent_details->getAgentId() +
"' software update failed. Agent is running previous software. Contact Check Point support.";
if (orchestration_status->getManifestError().find("Gateway was not fully deployed") == string::npos) {
orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::MANIFEST,
OrchestrationStatusResult::FAILED,
install_error
);
}
}
return self_update_status;
}
string packages_dir = getConfigurationWithDefault<string>(
"/etc/cp/packages",
"orchestration",
"Packages directory"
);
string current_installation_file = packages_dir + "/" + package_name + "/" + package_name;
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestHandler>();
bool is_clean_installation = !orchestration_tools->doesFileExist(current_installation_file);
auto package_handler = Singleton::Consume<I_PackageHandler>::by<ManifestHandler>();
if (!package_handler->shouldInstallPackage(package_name, package_handler_path)) {
current_packages.insert(make_pair(package_name, package));
dbgInfo(D_ORCHESTRATOR)
<< "Skipping installation of new package with the same version as current. Package: "
<< package_name;
continue;
}
bool current_result = true;
bool is_service = package.getType() == Package::PackageType::Service;
if (is_service) {
current_result = package_handler->preInstallPackage(package_name, package_handler_path);
}
current_result = current_result && package_handler->installPackage(
package_name,
package_handler_path,
false
);
if (current_result && is_service) {
current_result = package_handler->postInstallPackage(package_name, package_handler_path);
}
if (current_result && is_service) {
current_result = package_handler->updateSavedPackage(package_name, package_handler_path);
}
if (!current_result) {
string install_error;
if (is_clean_installation) {
string error_hostname_addition = "";
auto maybe_hostname = Singleton::Consume<I_DetailsResolver>::by<ManifestHandler>()->getHostname();
if (maybe_hostname.ok()) {
error_hostname_addition = " on host '" + maybe_hostname.unpack() + "'";
}
install_error =
"Critical Error: Agent/Gateway was not fully deployed" +
error_hostname_addition +
" and is not enforcing a security policy. Retry installation or contact Check Point support.";
} else {
auto agent_details = Singleton::Consume<I_AgentDetails>::by<ManifestHandler>();
install_error =
"Warning: Agent/Gateway '" +
agent_details->getAgentId() +
"' software update failed. Agent is running previous software. Contact Check Point support.";
}
corrupted_packages.insert(make_pair(package_name, package));
dbgWarning(D_ORCHESTRATOR) << "Failed to install package. Package: " << package_name;
auto orchestration_status = Singleton::Consume<I_OrchestrationStatus>::by<ManifestHandler>();
if (orchestration_status->getManifestError().find("Gateway was not fully deployed") == string::npos) {
orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::MANIFEST,
OrchestrationStatusResult::FAILED,
install_error
);
}
return false;
}
current_packages.insert(make_pair(package_name, package));
}
return true;
}
bool
ManifestHandler::uninstallPackage(Package &removed_package)
{
dbgDebug(D_ORCHESTRATOR) << "Starting uninstalling. Package: " << removed_package.getName();
string package_name = removed_package.getName();
string package_path = default_dir + "/" + package_name + "/" + package_name;
string installation_package = packages_dir + "/" + package_name + "/" + package_name;
auto package_handler = Singleton::Consume<I_PackageHandler>::by<ManifestHandler>();
return package_handler->uninstallPackage(package_name, package_path, installation_package);
}
bool
ManifestHandler::selfUpdate(
const Package &updated_package,
map<packageFilePath, Package> &current_packages,
const string &installation_file)
{
dbgInfo(D_ORCHESTRATOR) << "Updating orchestration service";
auto current_service = current_packages.find(updated_package.getName());
if (current_service != current_packages.end()) {
current_service->second = updated_package;
} else {
current_packages.insert(pair<packageFilePath, Package>(updated_package.getName(), updated_package));
}
string temp_manifest_path = manifest_file_path + temp_ext;
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ManifestHandler>();
if (!orchestration_tools->packagesToJsonFile(current_packages, temp_manifest_path)) {
dbgWarning(D_ORCHESTRATOR) << "Updating manifest temporary file has failed. File: " << temp_manifest_path;
return false;
}
string current_file = packages_dir + "/" + orch_service_name + "/" + orch_service_name;
string backup_file = current_file + backup_ext;
dbgDebug(D_ORCHESTRATOR) << "Saving the temporary backup file.";
if (orchestration_tools->doesFileExist(current_file)) {
dbgDebug(D_ORCHESTRATOR) << "Backup current installation package. Destination: " << backup_file;
if (!orchestration_tools->copyFile(current_file, backup_file + temp_ext)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to backup installation file. File: " << current_file;
return false;
}
} else {
dbgDebug(D_ORCHESTRATOR) << "There is no previous version for Orchestration";
}
string current_installation_file = current_file + temp_ext;
dbgDebug(D_ORCHESTRATOR) << "Saving the installation file: " << current_installation_file;
if (!orchestration_tools->copyFile(installation_file, current_installation_file)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to save the installation file: " << current_installation_file;
return false;
}
dbgDebug(D_ORCHESTRATOR) << "Starting to install the orchestration: " << current_installation_file;
auto package_handler = Singleton::Consume<I_PackageHandler>::by<ManifestHandler>();
return
package_handler->preInstallPackage(orch_service_name, current_installation_file) &&
package_handler->installPackage(orch_service_name, current_installation_file, false);
}
SASAL_END

View File

@@ -0,0 +1,10 @@
add_library(
orchestration_modules
orchestration_policy.cc
url_parser.cc
package.cc
orchestration_status.cc
data.cc
)
add_subdirectory(modules_ut)

View File

@@ -0,0 +1,52 @@
// 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 "orchestrator/data.h"
#include <map>
#include "sasal.h"
SASAL_START // Orchestration - Modules
using namespace std;
using namespace cereal;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
static const map<string, Data::ChecksumTypes> checksum_map = {
{ "sha1sum", Data::ChecksumTypes::SHA1 },
{ "sha256sum", Data::ChecksumTypes::SHA256 },
{ "sha512sum", Data::ChecksumTypes::SHA512 },
{ "md5sum", Data::ChecksumTypes::MD5 }
};
void
Data::serialize(JSONInputArchive &in_archive)
{
string checksum_type_as_string;
in_archive(make_nvp("checksumType", checksum_type_as_string));
if (checksum_map.find(checksum_type_as_string) != checksum_map.end()) {
checksum_type = checksum_map.at(checksum_type_as_string);
} else {
dbgWarning(D_ORCHESTRATOR) << "Unsupported checksum type: " << checksum_type_as_string;
return;
}
in_archive(
make_nvp("downloadPath", download_path),
make_nvp("checksum", checksum_value),
make_nvp("version", version)
);
}
SASAL_END

View File

@@ -0,0 +1,7 @@
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
orchestration_modules_ut
"orchestration_policy_ut.cc;url_parser_ut.cc;package_ut.cc;orchestration_status_ut.cc;data_ut.cc;"
"orchestration_modules;config;environment;metric;event_is;time_proxy;-lboost_regex"
)

View File

@@ -0,0 +1,85 @@
#include "orchestrator/data.h"
#include "cereal/types/string.hpp"
#include "cereal/archives/json.hpp"
#include <string>
#include <memory>
#include <fstream>
#include "cptest.h"
#include "customized_cereal_map.h"
using namespace testing;
using namespace std;
class DataTest : public Test
{
public:
bool
load(stringstream &string_stream, Data &data)
{
try {
cereal::JSONInputArchive archive_in(string_stream);
data.serialize(archive_in);
} catch (const cereal::Exception &) {
return false;
}
return true;
}
};
TEST_F(DataTest, doNothing)
{
}
TEST_F(DataTest, serializationFromString)
{
stringstream string_stream;
string_stream << "{"
" \"version\": \"c\","
" \"downloadPath\": \"https://a/data.json\",\n"
" \"checksumType\": \"sha1sum\","
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\""
"}";
bool res = false;
Data data;
try {
cereal::JSONInputArchive archive_in(string_stream);
data.serialize(archive_in);
res = true;
} catch (const cereal::Exception &) {
}
EXPECT_EQ(true, res);
EXPECT_EQ(Data::ChecksumTypes::SHA1, data.getChecksumType());
EXPECT_EQ("8d4a5709673a05b380ba7d6567e28910019118f5", data.getChecksum());
EXPECT_EQ("c", data.getVersion());
EXPECT_EQ("https://a/data.json", data.getDownloadPath());
}
TEST_F(DataTest, serializationFromStringAsMap)
{
stringstream string_stream;
string_stream << "{\n"
" \"ips\": {\n"
" \"version\": \"c\","
" \"downloadPath\": \"https://a/data.json\",\n"
" \"checksumType\": \"sha1sum\","
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\""
" }\n"
"}\n";
map<string, Data> data;
bool res = false;
try {
cereal::JSONInputArchive archive_in(string_stream);
cereal::load(archive_in, data);
res = true;
} catch (const cereal::Exception &e) {
}
EXPECT_EQ(true, res);
EXPECT_EQ(Data::ChecksumTypes::SHA1, data["ips"].getChecksumType());
EXPECT_EQ("8d4a5709673a05b380ba7d6567e28910019118f5", data["ips"].getChecksum());
EXPECT_EQ("c", data["ips"].getVersion());
EXPECT_EQ("https://a/data.json", data["ips"].getDownloadPath());
}

View File

@@ -0,0 +1,158 @@
#include "orchestration_policy.h"
#include <string>
#include <memory>
#include "cptest.h"
#include "cereal/types/string.hpp"
using namespace testing;
using namespace std;
class PolicyTest : public Test
{
public:
PolicyTest() {}
void
orchestrationPolicyToString(stringstream &string_stream)
{
cereal::JSONInputArchive archive_in(string_stream);
orchestration_policy.serialize(archive_in);
}
OrchestrationPolicy orchestration_policy;
};
TEST_F(PolicyTest, doNothing)
{
}
TEST_F(PolicyTest, serialization)
{
stringstream string_stream;
string_stream << "{"
" \"fog-address\": \"http://10.0.0.18:81/control/\","
" \"agent-type\": \"13324sadsd2\","
" \"pulling-interval\": 20,"
" \"error-pulling-interval\": 15"
"}";
try {
orchestrationPolicyToString(string_stream);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
EXPECT_EQ(15u, orchestration_policy.getErrorSleepInterval());
EXPECT_EQ(20u, orchestration_policy.getSleepInterval());
EXPECT_EQ("http://10.0.0.18:81/control/", orchestration_policy.getFogAddress());
}
TEST_F(PolicyTest, noAgentType)
{
stringstream string_stream;
string_stream << "{"
" \"fog-address\": \"http://10.0.0.18:81/control/\","
" \"agent-type\": \"\","
" \"pulling-interval\": 20,"
" \"error-pulling-interval\": 15"
"}";
try {
orchestrationPolicyToString(string_stream);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
EXPECT_EQ(15u, orchestration_policy.getErrorSleepInterval());
EXPECT_EQ(20u, orchestration_policy.getSleepInterval());
EXPECT_EQ("http://10.0.0.18:81/control/", orchestration_policy.getFogAddress());
}
TEST_F(PolicyTest, zeroSleepIntervels)
{
stringstream string_stream;
string_stream << "{"
" \"fog-address\": \"http://10.0.0.18:81/control/\","
" \"agent-type\": \"13324sadsd2\","
" \"pulling-interval\": 0,"
" \"error-pulling-interval\": 0"
"}";
try {
orchestrationPolicyToString(string_stream);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
EXPECT_EQ(0u, orchestration_policy.getErrorSleepInterval());
EXPECT_EQ(0u, orchestration_policy.getSleepInterval());
EXPECT_EQ("http://10.0.0.18:81/control/", orchestration_policy.getFogAddress());
}
TEST_F(PolicyTest, operatorEqual)
{
stringstream string_stream;
string_stream << "{"
" \"fog-address\": \"http://10.0.0.18:81/control/\","
" \"pulling-interval\": 20,"
" \"error-pulling-interval\": 15"
"}";
try {
orchestrationPolicyToString(string_stream);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
OrchestrationPolicy orchestration_copy_policy;
stringstream string_stream_copy;
string_stream_copy << "{"
" \"fog-address\": \"http://10.0.0.18:81/control/\","
" \"pulling-interval\": 20,"
" \"error-pulling-interval\": 15"
"}";
try{
cereal::JSONInputArchive archive_in(string_stream_copy);
orchestration_copy_policy.serialize(archive_in);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
EXPECT_TRUE(orchestration_copy_policy == orchestration_policy);
EXPECT_FALSE(orchestration_copy_policy != orchestration_policy);
OrchestrationPolicy orchestration_new_policy;
stringstream string_stream_new;
string_stream_new << "{"
" \"fog-address\": \"http://10.0.0.18:801/control/\","
" \"pulling-interval\": 20,"
" \"error-pulling-interval\": 15"
"}";
try{
cereal::JSONInputArchive archive_in(string_stream_new);
orchestration_new_policy.serialize(archive_in);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
EXPECT_FALSE(orchestration_new_policy == orchestration_policy);
EXPECT_TRUE(orchestration_new_policy != orchestration_policy);
}
TEST_F(PolicyTest, newOptionalFields)
{
stringstream string_stream;
string_stream << "{"
" \"fog-address\": \"https://fog-api-gw-agents.cloud.ngen.checkpoint.com\","
" \"pulling-interval\": 30,"
" \"error-pulling-interval\": 10,"
" \"agent-type\": \"arrow\""
"}";
try {
orchestrationPolicyToString(string_stream);
} catch (cereal::Exception &e) {
ASSERT_TRUE(false) << "Cereal threw an exception: " << e.what();
}
EXPECT_EQ(10u, orchestration_policy.getErrorSleepInterval());
EXPECT_EQ(30u, orchestration_policy.getSleepInterval());
EXPECT_EQ("https://fog-api-gw-agents.cloud.ngen.checkpoint.com", orchestration_policy.getFogAddress());
}

View File

@@ -0,0 +1,484 @@
#include "orchestration_status.h"
#include <string>
#include <chrono>
#include <fstream>
#include <map>
#include "cptest.h"
#include "config.h"
#include "config_component.h"
#include "mock/mock_time_get.h"
#include "mock/mock_orchestration_tools.h"
#include "mock/mock_agent_details.h"
#include "mock/mock_mainloop.h"
#include "mock/mock_rest_api.h"
using namespace testing;
using namespace std;
using namespace chrono;
class OrchestrationStatusTest : public Test
{
public:
~OrchestrationStatusTest() { Debug::setNewDefaultStdout(&cout); }
void
init()
{
Debug::setUnitTestFlag(D_ORCHESTRATOR, Debug::DebugLevel::TRACE);
Debug::setNewDefaultStdout(&capture_debug);
CPTestTempfile status_file;
file_path = status_file.fname;
setConfiguration(file_path, "orchestration", "Orchestration status path");
// Write orchestration status to file routine
EXPECT_CALL(
mock_mainloop,
addRecurringRoutine(I_MainLoop::RoutineType::Timer, chrono::microseconds(5000000), _, _, false))
.WillOnce(DoAll(SaveArg<2>(&routine), Return(1))
);
EXPECT_CALL(mock_tools, readFile(file_path)).WillOnce(Return(start_file_content));
orchestration_status.init();
}
string
orchestrationStatusFileToString()
{
routine();
ifstream status_file(file_path);
stringstream string_stream;
if (status_file.is_open()) {
string line;
bool is_first_line = true;
while (getline(status_file, line)) {
if (is_first_line) {
is_first_line = false;
} else {
string_stream << endl;
}
string_stream << line;
}
status_file.close();
}
return string_stream.str();
}
string
buildOrchestrationStatusJSON(
const string &last_update_attempt = "None",
const string &last_update_status = "None",
const string &last_update = "None",
const string &last_manifest_update = "None",
const string &policy_version = "",
const string &last_policy_update = "None",
const string &last_settings_update = "None",
const string &upgrade_mode = "None",
const string &fog_address = "None",
const string &registration_status = "None",
const string &manifest_status = "None",
const string &registration_details_name = "",
const string &registration_details_type = "",
const string &registration_details_platform = "",
const string &registration_details_architecture = "",
const string &agent_id = "None",
const string &profile_id = "None",
const string &tenant_id = "None"
)
{
return "{\n"
" \"Last update attempt\": \"" + last_update_attempt + "\",\n"
" \"Last update status\": \"" + last_update_status + "\",\n"
" \"Last update\": \"" + last_update + "\",\n"
" \"Last manifest update\": \"" + last_manifest_update + "\",\n"
" \"Policy version\": \"" + policy_version + "\",\n"
" \"Last policy update\": \"" + last_policy_update + "\",\n"
" \"Last settings update\": \"" + last_settings_update + "\",\n"
" \"Upgrade mode\": \"" + upgrade_mode + "\",\n"
" \"Fog address\": \"" + fog_address + "\",\n"
" \"Registration status\": \"" + registration_status + "\",\n"
" \"Registration details\": {\n"
" \"Name\": \"" + registration_details_name + "\",\n"
" \"Type\": \"" + registration_details_type + "\",\n"
" \"Platform\": \"" + registration_details_platform + "\",\n"
" \"Architecture\": \"" + registration_details_architecture + "\"\n"
" },\n"
" \"Agent ID\": \"" + agent_id + "\",\n"
" \"Profile ID\": \"" + profile_id + "\",\n"
" \"Tenant ID\": \"" + tenant_id + "\",\n"
" \"Manifest status\": \"" + manifest_status + "\",\n"
" \"Service policy\": {},\n"
" \"Service settings\": {}\n"
"}";
}
::Environment env;
ConfigComponent config;
StrictMock<MockTimeGet> time;
StrictMock<MockMainLoop> mock_mainloop;
ostringstream capture_debug;
StrictMock<MockOrchestrationTools> mock_tools;
StrictMock<MockAgentDetails> mock_agent_details;
OrchestrationStatus orchestration_status;
I_OrchestrationStatus * i_orchestration_status =
Singleton::Consume<I_OrchestrationStatus>::from(orchestration_status);
string file_path;
Maybe<string> start_file_content = genError("No file");
I_MainLoop::Routine routine;
};
TEST_F(OrchestrationStatusTest, doNothing)
{
}
TEST_F(OrchestrationStatusTest, noFieldsValues)
{
init();
auto result = orchestrationStatusFileToString();
EXPECT_EQ(buildOrchestrationStatusJSON(), result);
}
TEST_F(OrchestrationStatusTest, recoverFields)
{
init();
auto result = orchestrationStatusFileToString();
i_orchestration_status->recoverFields();
EXPECT_EQ(orchestrationStatusFileToString(), result);
}
TEST_F(OrchestrationStatusTest, loadFromFile)
{
Maybe<string> status = genError("No file");;
CPTestTempfile status_file;
file_path = status_file.fname;
setConfiguration(file_path, "orchestration", "Orchestration status path");
// Write to file routine
EXPECT_CALL(
mock_mainloop,
addRecurringRoutine(I_MainLoop::RoutineType::Timer, chrono::microseconds(5000000), _, _, false)
).Times(3).WillRepeatedly(DoAll(SaveArg<2>(&routine), Return(1)));
EXPECT_CALL(mock_tools, readFile(file_path)).Times(3).WillRepeatedly(Return(status));
orchestration_status.init();
status = orchestrationStatusFileToString();
orchestration_status.init();
EXPECT_EQ(orchestrationStatusFileToString(), status.unpack());
EXPECT_CALL(time, getLocalTimeStr())
.WillOnce(Return(string("attempt time")))
.WillOnce(Return(string("current time")));
i_orchestration_status->setLastUpdateAttempt();
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
status = orchestrationStatusFileToString();
EXPECT_EQ(buildOrchestrationStatusJSON("attempt time", "Succeeded ", "current time"), status.unpack());
// Write status to file
routine();
// Reload status from file and validate status
orchestration_status.init();
EXPECT_EQ(buildOrchestrationStatusJSON("attempt time", "Succeeded ", "current time"), status.unpack());
}
TEST_F(OrchestrationStatusTest, checkUpdateStatus)
{
init();
EXPECT_CALL(time, getLocalTimeStr())
.WillOnce(Return(string("attempt time")))
.WillOnce(Return(string("current time")));
i_orchestration_status->setLastUpdateAttempt();
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
auto result = orchestrationStatusFileToString();
EXPECT_EQ(buildOrchestrationStatusJSON("attempt time", "Succeeded ", "current time"), result);
}
TEST_F(OrchestrationStatusTest, recoveryFields)
{
init();
CPTestTempfile status({""});
setConfiguration(status.fname, "orchestration", "Orchestration status path");
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::SUCCESS
);
const string agent_id = "AgentId";
const string profile_id = "ProfileId";
const string tenant_id = "TenantId";
auto fog_addr = Maybe<string>(string("FogDomain"));
EXPECT_CALL(mock_agent_details, getAgentId()).WillOnce(Return(agent_id));
EXPECT_CALL(mock_agent_details, getProfileId()).WillOnce(Return(profile_id));
EXPECT_CALL(mock_agent_details, getTenantId()).WillOnce(Return(tenant_id));
EXPECT_CALL(mock_agent_details, getFogDomain()).WillOnce(Return(fog_addr));
i_orchestration_status->writeStatusToFile();
EXPECT_THAT(capture_debug.str(), HasSubstr("Repairing status fields"));
EXPECT_EQ(i_orchestration_status->getAgentId(), agent_id);
EXPECT_EQ(i_orchestration_status->getProfileId(), profile_id);
EXPECT_EQ(i_orchestration_status->getTenantId(), tenant_id);
EXPECT_EQ(i_orchestration_status->getFogAddress(), fog_addr.unpack());
}
TEST_F(OrchestrationStatusTest, updateAllLastUpdatesTypes)
{
init();
EXPECT_CALL(time, getLocalTimeStr())
.WillOnce(Return(string("attempt time")))
.WillOnce(Return(string("current time")))
.WillOnce(Return(string("current time001")));
i_orchestration_status->setLastUpdateAttempt();
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
i_orchestration_status->setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool>(true, false, false)
);
auto result = orchestrationStatusFileToString();
EXPECT_EQ(buildOrchestrationStatusJSON("attempt time", "Succeeded ", "current time", "current time001"), result);
EXPECT_CALL(time, getLocalTimeStr())
.Times(2)
.WillRepeatedly(Return(string("current time002")));
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
i_orchestration_status->setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool>(true, true, false)
);
result = orchestrationStatusFileToString();
EXPECT_EQ(
buildOrchestrationStatusJSON(
"attempt time",
"Succeeded ",
"current time002",
"current time002",
"",
"current time002"
),
result
);
EXPECT_CALL(time, getLocalTimeStr())
.Times(2)
.WillRepeatedly(Return(string("current time003")));
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
i_orchestration_status->setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool>(true, true, true)
);
result = orchestrationStatusFileToString();
EXPECT_EQ(
buildOrchestrationStatusJSON(
"attempt time",
"Succeeded ",
"current time003",
"current time003",
"",
"current time003",
"current time003"
),
result
);
}
TEST_F(OrchestrationStatusTest, errorInRegistrationAndMainfest)
{
init();
string fog_address = "http://fog.address";
string registar_error = "Fail to registar";
string manifest_error = "Fail to achieve manifest";
string last_update_error = "Fail to update";
EXPECT_CALL(time, getLocalTimeStr()).Times(3).WillRepeatedly(Return(string("Time")));
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
i_orchestration_status->setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool>(true, true, true)
);
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::FAILED,
last_update_error
);
i_orchestration_status->setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool>(false, false, false)
);
i_orchestration_status->setUpgradeMode("Online upgrades");
i_orchestration_status->setFogAddress(fog_address);
i_orchestration_status->setUpgradeMode("Online upgrades");
i_orchestration_status->setFogAddress(fog_address);
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::FAILED,
registar_error
);
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::MANIFEST,
OrchestrationStatusResult::FAILED,
manifest_error
);
EXPECT_EQ(i_orchestration_status->getManifestError(), manifest_error);
auto result = orchestrationStatusFileToString();
EXPECT_EQ(
buildOrchestrationStatusJSON(
"None",
"Failed. Reason: " + last_update_error,
"Time",
"Time",
"",
"Time",
"Time",
"Online upgrades",
fog_address,
"Failed. Reason: " + registar_error,
"Failed. Reason: " + manifest_error
),
result
);
}
TEST_F(OrchestrationStatusTest, setAllFields)
{
init();
string fog_address = "http://fog.address";
EXPECT_CALL(time, getLocalTimeStr())
.Times(3)
.WillRepeatedly(Return(string("current time")));
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::LAST_UPDATE,
OrchestrationStatusResult::SUCCESS
);
i_orchestration_status->setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool>(true, true, true)
);
i_orchestration_status->setRegistrationDetails("name", "type", "platform", "arch");
i_orchestration_status->setAgentDetails("id", "profile", "tenant");
i_orchestration_status->setFogAddress("http://fog.address");
i_orchestration_status->setPolicyVersion("12");
i_orchestration_status->setAgentType("test_type");
i_orchestration_status->setUpgradeMode("Test Mode");
i_orchestration_status->setRegistrationStatus("Succeeded");
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::SUCCESS
);
i_orchestration_status->setFieldStatus(
OrchestrationStatusFieldType::MANIFEST,
OrchestrationStatusResult::SUCCESS
);
string non_empty_conf = "{x:y}";
string curr_mock_path = "path";
EXPECT_CALL(mock_tools, readFile(curr_mock_path)).WillRepeatedly(Return(non_empty_conf));
EXPECT_CALL(mock_tools, readFile(string("new_path"))).WillOnce(Return(string("{}")));
i_orchestration_status->setServiceConfiguration(
"service_a", "path", OrchestrationStatusConfigType::SETTINGS
);
i_orchestration_status->setServiceConfiguration(
"service_b", "path", OrchestrationStatusConfigType::POLICY
);
i_orchestration_status->setServiceConfiguration(
"service_c", "path", OrchestrationStatusConfigType::POLICY
);
i_orchestration_status->setServiceConfiguration(
"service_c", "new_path", OrchestrationStatusConfigType::POLICY
);
i_orchestration_status->setLastUpdateAttempt();
auto result = orchestrationStatusFileToString();
string expected = "{\n"
" \"Last update attempt\": \"current time\",\n"
" \"Last update status\": \"Succeeded \",\n"
" \"Last update\": \"current time\",\n"
" \"Last manifest update\": \"current time\",\n"
" \"Policy version\": \"12\",\n"
" \"Last policy update\": \"current time\",\n"
" \"Last settings update\": \"current time\",\n"
" \"Upgrade mode\": \"Test Mode\",\n"
" \"Fog address\": \"http://fog.address\",\n"
" \"Registration status\": \"Succeeded \",\n"
" \"Registration details\": {\n"
" \"Name\": \"name\",\n"
" \"Type\": \"test_type\",\n"
" \"Platform\": \"platform\",\n"
" \"Architecture\": \"arch\"\n"
" },\n"
" \"Agent ID\": \"id\",\n"
" \"Profile ID\": \"profile\",\n"
" \"Tenant ID\": \"tenant\",\n"
" \"Manifest status\": \"Succeeded \",\n"
" \"Service policy\": {\n"
" \"service_b\": \"path\"\n"
" },\n"
" \"Service settings\": {\n"
" \"service_a\": \"path\"\n"
" }\n"
"}";
EXPECT_EQ(expected, result);
// Now lets check load from file
routine();
EXPECT_EQ(expected, orchestrationStatusFileToString());
EXPECT_CALL(
mock_mainloop,
addRecurringRoutine(I_MainLoop::RoutineType::Timer, chrono::microseconds(5000000), _, _, false))
.WillOnce(DoAll(SaveArg<2>(&routine), Return(1)));
EXPECT_CALL(mock_tools, readFile(file_path)).Times(1).WillOnce(Return(expected));
orchestration_status.init();
EXPECT_EQ(expected, orchestrationStatusFileToString());
map<string, string> service_map_a = {{"service_a", "path"}};
map<string, string> service_map_b = {{"service_b", "path"}};
string agent_details =
"\n Name: name"
"\n Type: test_type"
"\n Platform: platform"
"\n Architecture: arch";
EXPECT_EQ(i_orchestration_status->getLastUpdateAttempt(), "current time");
EXPECT_EQ(i_orchestration_status->getUpdateStatus(), "Succeeded ");;
EXPECT_EQ(i_orchestration_status->getUpdateTime(), "current time");
EXPECT_EQ(i_orchestration_status->getLastManifestUpdate(), "current time");
EXPECT_EQ(i_orchestration_status->getPolicyVersion(), "12");
EXPECT_EQ(i_orchestration_status->getLastPolicyUpdate(), "current time");
EXPECT_EQ(i_orchestration_status->getLastSettingsUpdate(), "current time");
EXPECT_EQ(i_orchestration_status->getUpgradeMode(), "Test Mode");
EXPECT_EQ(i_orchestration_status->getFogAddress(), "http://fog.address");
EXPECT_EQ(i_orchestration_status->getRegistrationStatus(), "Succeeded ");
EXPECT_EQ(i_orchestration_status->getAgentId(), "id");
EXPECT_EQ(i_orchestration_status->getProfileId(), "profile");
EXPECT_EQ(i_orchestration_status->getTenantId(), "tenant");
EXPECT_EQ(i_orchestration_status->getManifestStatus(), "Succeeded ");
EXPECT_EQ(i_orchestration_status->getServicePolicies(), service_map_b);
EXPECT_EQ(i_orchestration_status->getServiceSettings(), service_map_a);
EXPECT_EQ(i_orchestration_status->getRegistrationDetails(), agent_details);
}

View File

@@ -0,0 +1,236 @@
#include "package.h"
#include "cptest.h"
#include "cereal/types/string.hpp"
#include "cereal/archives/json.hpp"
#include <string>
#include <memory>
#include <fstream>
using namespace testing;
using namespace std;
class PackageTest : public Test
{
public:
PackageTest() {}
bool
load(stringstream &string_stream, Package &package)
{
try {
cereal::JSONInputArchive archive_in(string_stream);
package.serialize(archive_in);
} catch (const cereal::Exception &) {
return false;
}
return true;
}
void
write(const string &path, Package &package)
{
std::ofstream os(path);
cereal::JSONOutputArchive archive_out(os);
package.serialize(archive_out);
}
string
readFile(const string &path)
{
ifstream text_file(path);
stringstream buffer;
buffer << text_file.rdbuf();
return buffer.str();
}
};
TEST_F(PackageTest, doNothing)
{
}
TEST_F(PackageTest, serializationFromString)
{
stringstream string_stream;
string_stream << "{"
" \"version\": \"c\","
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"name\": \"orchestration\","
" \"checksum-type\": \"sha1sum\","
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\","
" \"package-type\": \"service\","
" \"require\": []"
"}";
Package package;
EXPECT_EQ(true, load(string_stream, package));
vector<string> links = { "https://10.0.0.18/install_orchestration.sh", "ftp://172.23.92.135/policy.txt" };
EXPECT_EQ("orchestration", package.getName());
EXPECT_EQ(Package::ChecksumTypes::SHA1, package.getChecksumType());
EXPECT_EQ("8d4a5709673a05b380ba7d6567e28910019118f5", package.getChecksum());
EXPECT_EQ("orchestration", package.getName());
EXPECT_EQ("c", package.getVersion());
EXPECT_EQ(Package::PackageType::Service, package.getType());
EXPECT_TRUE(package.isInstallable().ok());
}
TEST_F(PackageTest, writeAsJson)
{
stringstream string_stream;
string_stream << "{\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"name\": \"orchestration\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\"\n"
"}";
Package package;
EXPECT_EQ(true, load(string_stream, package));
vector<string> links = { "https://10.0.0.18/install_orchestration.sh", "ftp://172.23.92.135/policy.txt" };
EXPECT_EQ("orchestration", package.getName());
EXPECT_EQ(Package::ChecksumTypes::SHA1, package.getChecksumType());
EXPECT_EQ("8d4a5709673a05b380ba7d6567e28910019118f5", package.getChecksum());
EXPECT_EQ("orchestration", package.getName());
EXPECT_EQ("c", package.getVersion());
EXPECT_EQ(Package::PackageType::Service, package.getType());
EXPECT_TRUE(package.isInstallable().ok());
write("service.json", package);
string data = readFile("service.json");
EXPECT_EQ(string_stream.str(), data);
}
TEST_F(PackageTest, eqService)
{
stringstream string_stream;
string_stream << "{\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"name\": \"orchestration\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\"\n"
"}";
Package package;
Package package2;
EXPECT_TRUE(load(string_stream, package));
string_stream.clear();
string_stream << "{\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"name\": \"orchestration\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910000000000\",\n"
" \"package-type\": \"service\"\n"
"}";
EXPECT_TRUE(load(string_stream, package));
EXPECT_TRUE(package != package2);
}
TEST_F(PackageTest, changeDir)
{
stringstream string_stream;
string_stream << "{\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"name\": \"../..\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\"\n"
"}";
Package package;
EXPECT_FALSE(load(string_stream, package));
}
TEST_F(PackageTest, mkdirCommand)
{
stringstream string_stream;
string_stream << "{\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"name\": \"mkdir ../../something\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\"\n"
"}";
Package package;
EXPECT_FALSE(load(string_stream, package));
}
TEST_F(PackageTest, badPackageName)
{
stringstream string_stream;
string_stream << "{\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"name\": \"- - - - - -\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\"\n"
"}";
Package package;
EXPECT_FALSE(load(string_stream, package));
}
TEST_F(PackageTest, anyOrder)
{
stringstream string_stream;
string_stream << "{\n"
" \"name\": \"asdQwe\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"version\": \"c\",\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\",\n"
" \"checksum-type\": \"sha1sum\"\n"
"}";
Package package;
EXPECT_TRUE(load(string_stream, package));
}
TEST_F(PackageTest, anyOrderWithRequire)
{
stringstream string_stream;
string_stream << "{\n"
" \"require\": [],\n"
" \"name\": \"asdQwe\",\n"
" \"version\": \"c\",\n"
" \"relative-path\": \"/install_orchestration.sh\",\n"
" \"download-path\": \"https://a/install_orchestration.sh\",\n"
" \"checksum\": \"8d4a5709673a05b380ba7d6567e28910019118f5\",\n"
" \"package-type\": \"service\",\n"
" \"checksum-type\": \"sha1sum\"\n"
"}";
Package package;
EXPECT_TRUE(load(string_stream, package));
}
TEST_F(PackageTest, uninstallablePackage)
{
stringstream string_stream;
string_stream << "{\n"
" \"name\": \"waap\",\n"
" \"version\": \"\",\n"
" \"download-path\": \"\",\n"
" \"relative-path\": \"\",\n"
" \"checksum\": \"\",\n"
" \"package-type\": \"service\",\n"
" \"checksum-type\": \"sha1sum\",\n"
" \"status\": false,\n"
" \"message\": \"This security app isn't valid for this agent\"\n"
"}";
Package package;
EXPECT_TRUE(load(string_stream, package));
EXPECT_THAT(package.isInstallable(), IsError("This security app isn't valid for this agent"));
}

View File

@@ -0,0 +1,117 @@
#include "url_parser.h"
#include "cptest.h"
#include "mock/mock_orchestration_tools.h"
#include <string>
#include <memory>
using namespace testing;
using namespace std;
class URLParserTest : public Test
{
public:
URLParserTest() {}
StrictMock<MockOrchestrationTools> mock_orchestration_tools;
};
TEST_F(URLParserTest, doNothing)
{
}
TEST_F(URLParserTest, parseHTTP)
{
URLParser link("http://172.23.92.180:180/something");
EXPECT_FALSE(link.isOverSSL());
EXPECT_EQ("180", link.getPort());
EXPECT_EQ("/something", link.getQuery());
}
TEST_F(URLParserTest, parseHTTPS)
{
URLParser link("https://172.23.92.180:180/something");
EXPECT_TRUE(link.isOverSSL());
EXPECT_EQ("180", link.getPort());
EXPECT_EQ("/something", link.getQuery());
}
TEST_F(URLParserTest, parseAWS)
{
URLParser link("https://a58efa94efdf711e8a6540620a59b447-1878332922.eu-west-1.elb.amazonaws.com/");
EXPECT_TRUE(link.isOverSSL());
EXPECT_EQ("443", link.getPort());
EXPECT_EQ("a58efa94efdf711e8a6540620a59b447-1878332922.eu-west-1.elb.amazonaws.com", link.getBaseURL().unpack());
EXPECT_EQ("", link.getQuery());
}
TEST_F(URLParserTest, parseAWSWithoutSlash)
{
URLParser link("https://a58efa94efdf711e8a6540620a59b447-1878332922.eu-west-1.elb.amazonaws.com");
EXPECT_TRUE(link.isOverSSL());
EXPECT_EQ("443", link.getPort());
EXPECT_EQ("a58efa94efdf711e8a6540620a59b447-1878332922.eu-west-1.elb.amazonaws.com", link.getBaseURL().unpack());
EXPECT_EQ("", link.getQuery());
}
TEST_F(URLParserTest, protocolIsMissing)
{
// HTTPS is set by default when protocol is not present in URL.
URLParser link("a58efa94efdf711e8a6540620a59b447-1878332922.eu-west-1.elb.amazonaws.com");
EXPECT_EQ(link.getBaseURL().unpack(), "a58efa94efdf711e8a6540620a59b447-1878332922.eu-west-1.elb.amazonaws.com");
EXPECT_TRUE(link.isOverSSL());
EXPECT_EQ("443", link.getPort());
EXPECT_EQ("", link.getQuery());
}
TEST_F(URLParserTest, parseBadURL)
{
URLParser link("http://this_is_not_https_site.com/something");
EXPECT_FALSE(link.isOverSSL());
EXPECT_EQ("80", link.getPort());
EXPECT_EQ("this_is_not_https_site.com", link.getBaseURL().unpack());
EXPECT_EQ("/something", link.getQuery());
}
TEST_F(URLParserTest, parseNothing)
{
URLParser link("");
EXPECT_FALSE(link.getBaseURL().ok());
EXPECT_TRUE(link.isOverSSL());
EXPECT_EQ("443", link.getPort());
EXPECT_EQ("", link.getQuery());
}
TEST_F(URLParserTest, copyCtr)
{
URLParser link("");
URLParser copy_link = link;
EXPECT_TRUE(copy_link.isOverSSL());
EXPECT_EQ("443", copy_link.getPort());
EXPECT_EQ("", copy_link.getQuery());
}
TEST_F(URLParserTest, printTest)
{
string url_path = "this_is_test_url";
URLParser link(url_path);
EXPECT_EQ("https://" + url_path + ":443", link.toString());
stringstream ss;
ss << link;
EXPECT_EQ("https://" + url_path + ":443", ss.str());
}
TEST_F(URLParserTest, setQuery)
{
string url_path = "this_is_test_url/test.sh";
URLParser link(url_path);
EXPECT_EQ("https://" + url_path + ":443", link.toString());
link.setQuery("/new-query");
EXPECT_EQ("https://this_is_test_url/new-query:443", link.toString());
}

View File

@@ -0,0 +1,64 @@
// 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 "orchestration_policy.h"
#include "sasal.h"
SASAL_START // Orchestration - Modules
using namespace std;
using namespace cereal;
const string &
OrchestrationPolicy::getFogAddress() const
{
return fog_address;
}
const unsigned long &
OrchestrationPolicy::getSleepInterval() const
{
return sleep_interval;
}
const unsigned long &
OrchestrationPolicy::getErrorSleepInterval() const
{
return error_sleep_interval;
}
void
OrchestrationPolicy::serialize(JSONInputArchive &archive)
{
// Split it, so the order doesn't matter.
archive(make_nvp("fog-address", fog_address));
archive(make_nvp("pulling-interval", sleep_interval));
archive(make_nvp("error-pulling-interval", error_sleep_interval));
}
bool
OrchestrationPolicy::operator==(const OrchestrationPolicy &other) const
{
return error_sleep_interval == other.error_sleep_interval &&
sleep_interval == other.sleep_interval &&
fog_address == other.fog_address;
}
bool
OrchestrationPolicy::operator!=(const OrchestrationPolicy &other) const
{
return !((*this) == other);
}
SASAL_END

View File

@@ -0,0 +1,685 @@
// 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 "orchestration_status.h"
#include <string>
#include <chrono>
#include <algorithm>
#include "debug.h"
#include "config.h"
#include "sasal.h"
using namespace cereal;
using namespace std;
using namespace chrono;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
SASAL_START // Orchestration - Modules
class RegistrationDetails
{
public:
RegistrationDetails() = default;
RegistrationDetails(const RegistrationDetails &) = default;
RegistrationDetails(RegistrationDetails &&) = default;
RegistrationDetails(
string name,
string type,
string platform,
string architecture)
:
name(name),
type(type),
platform(platform),
architecture(architecture)
{}
void
serialize(cereal::JSONOutputArchive &archive)
{
if (type == "InfinityNextGateway") {
type = "AppSecGateway";
}
archive(
cereal::make_nvp("Name", name),
cereal::make_nvp("Type", type),
cereal::make_nvp("Platform", platform),
cereal::make_nvp("Architecture", architecture)
);
}
void
serialize(cereal::JSONInputArchive &archive)
{
archive(
cereal::make_nvp("Name", name),
cereal::make_nvp("Type", type),
cereal::make_nvp("Platform", platform),
cereal::make_nvp("Architecture", architecture)
);
if (type == "InfinityNextGateway") {
type = "AppSecGateway";
}
}
RegistrationDetails & operator=(const RegistrationDetails &) = default;
RegistrationDetails & operator=(RegistrationDetails &&) = default;
void setAgentType(const string &_type) { type = _type; }
string
toString() const
{
return
"\n Name: " + name +
"\n Type: " + type +
"\n Platform: " + platform +
"\n Architecture: " + architecture;
}
private:
string name;
string type;
string platform;
string architecture;
};
class Status
{
public:
Status() = default;
Status(const Status &) = default;
Status(Status &&) = default;
Status & operator=(Status &&from) = default;
Status & operator=(const Status &from)
{
last_update_status = from.last_update_status;
last_update_time = from.last_update_time;
last_update_attempt = from.last_update_attempt;
last_manifest_update = from.last_manifest_update;
policy_version = from.policy_version;
last_policy_update = from.last_policy_update;
last_settings_update = from.last_settings_update;
upgrade_mode = from.upgrade_mode;
fog_address = from.fog_address;
registration_status = from.registration_status;
manifest_status = from.manifest_status;
agent_id = from.agent_id;
profile_id = from.profile_id;
tenant_id = from.tenant_id;
registration_details = from.registration_details;
service_policies = from.service_policies;
service_settings = from.service_settings;
return *this;
}
const string & getLastUpdateAttempt() const { return last_update_attempt; }
const string & getUpdateStatus() const { return last_update_status; }
const string & getUpdateTime() const { return last_update_time; }
const string & getLastManifestUpdate() const { return last_manifest_update; }
const string & getPolicyVersion() const { return policy_version; }
const string & getLastPolicyUpdate() const { return last_policy_update; }
const string & getLastSettingsUpdate() const { return last_settings_update; }
const string & getUpgradeMode() const { return upgrade_mode; }
const string & getFogAddress() const { return fog_address; }
const string & getRegistrationStatus() const { return registration_status; }
const string & getAgentId() const { return agent_id; }
const string & getProfileId() const { return profile_id; }
const string & getTenantId() const { return tenant_id; }
const string & getManifestStatus() const { return manifest_status; }
const string & getManifestError() const { return manifest_error; }
const RegistrationDetails & getRegistrationDetails() const { return registration_details; }
const map<string, string> & getServicePolicies() const { return service_policies; }
const map<string, string> & getServiceSettings() const { return service_settings; }
void
insertServicePolicy(const string &key, const string &value)
{
service_policies.insert(make_pair(key, value));
}
void
eraseServicePolicy(const string &key)
{
service_policies.erase(key);
}
void
insertServiceSetting(const string &key, const string &value)
{
service_settings.insert(make_pair(key, value));
}
void
eraseServiceSetting(const string &key)
{
service_settings.erase(key);
}
void
setIsConfigurationUpdated(
EnumArray<OrchestrationStatusConfigType, bool> config_types,
const string &current_time
)
{
if (config_types[OrchestrationStatusConfigType::MANIFEST]) last_manifest_update = current_time;
if (config_types[OrchestrationStatusConfigType::POLICY]) last_policy_update = current_time;
if (config_types[OrchestrationStatusConfigType::SETTINGS]) last_settings_update = current_time;
}
void
setPolicyVersion(const string &_policy_version)
{
policy_version = _policy_version;
}
void
setRegistrationStatus(const string &_reg_status)
{
registration_status = _reg_status;
}
void
setUpgradeMode(const string &_upgrade_mode)
{
upgrade_mode = _upgrade_mode;
}
void
setAgentType(const string &_agent_type)
{
registration_details.setAgentType(_agent_type);
}
void
setAgentDetails(
const string &_agent_id,
const string &_profile_id,
const string &_tenant_id)
{
agent_id = _agent_id;
profile_id = _profile_id;
tenant_id = _tenant_id;
}
void
setLastUpdateAttempt(const string &_last_update_attempt)
{
last_update_attempt = _last_update_attempt;
}
void
setFogAddress(const string &_fog_address)
{
fog_address = _fog_address;
}
void
setRegistrationDetails(
const string &name,
const string &type,
const string &platform,
const string &arch)
{
registration_details = RegistrationDetails(name, type, platform, arch);
}
void
setManifestStatus(const string &_manifest_status)
{
manifest_status = _manifest_status;
}
void
setManifestError(const string &error)
{
manifest_error = error;
}
void
setLastUpdateTime(const string &_last_update_time)
{
last_update_time = _last_update_time;
}
void
setLastUpdateStatus(const string &_last_update_status)
{
last_update_status = _last_update_status;
}
void
initValues()
{
last_update_attempt = "None";
last_update_time = "None";
last_update_status = "None";
last_manifest_update = "None";
last_policy_update = "None";
last_settings_update = "None";
fog_address = "None";
agent_id = "None";
profile_id = "None";
tenant_id = "None";
registration_status = "None";
manifest_status = "None";
upgrade_mode = "None";
}
void
recoverFields()
{
auto success_status = "Succeeded";
if (fog_address == "None" && registration_status.find(success_status) != string::npos) {
auto agent_details = Singleton::Consume<I_AgentDetails>::by<OrchestrationStatus>();
dbgWarning(D_ORCHESTRATOR) << "Repairing status fields";
agent_id = agent_details->getAgentId();
profile_id = agent_details->getProfileId();
tenant_id = agent_details->getTenantId();
auto maybe_fog_domain = agent_details->getFogDomain();
if (maybe_fog_domain.ok()) {
fog_address = maybe_fog_domain.unpack();
} else {
fog_address = "None";
}
}
}
void
serialize(cereal::JSONOutputArchive &archive)
{
recoverFields();
archive(cereal::make_nvp("Last update attempt", last_update_attempt));
archive(cereal::make_nvp("Last update status", last_update_status));
archive(cereal::make_nvp("Last update", last_update_time));
archive(cereal::make_nvp("Last manifest update", last_manifest_update));
archive(cereal::make_nvp("Policy version", policy_version));
archive(cereal::make_nvp("Last policy update", last_policy_update));
archive(cereal::make_nvp("Last settings update", last_settings_update));
archive(cereal::make_nvp("Upgrade mode", upgrade_mode));
archive(cereal::make_nvp("Fog address", fog_address));
archive(cereal::make_nvp("Registration status", registration_status));
archive(cereal::make_nvp("Registration details", registration_details));
archive(cereal::make_nvp("Agent ID", agent_id));
archive(cereal::make_nvp("Profile ID", profile_id));
archive(cereal::make_nvp("Tenant ID", tenant_id));
archive(cereal::make_nvp("Manifest status", manifest_status));
archive(cereal::make_nvp("Service policy", service_policies));
archive(cereal::make_nvp("Service settings", service_settings));
}
void
serialize(cereal::JSONInputArchive &archive)
{
archive(cereal::make_nvp("Last update attempt", last_update_attempt));
archive(cereal::make_nvp("Last update status", last_update_status));
archive(cereal::make_nvp("Last update", last_update_time));
archive(cereal::make_nvp("Last manifest update", last_manifest_update));
try {
archive(cereal::make_nvp("Policy version", policy_version));
} catch (...) {
archive.setNextName(nullptr);
}
archive(cereal::make_nvp("Last policy update", last_policy_update));
archive(cereal::make_nvp("Last settings update", last_settings_update));
// Optional param (upgrade - new parameter)
bool is_upgrade_mode = false;
try {
archive(cereal::make_nvp("Upgrade mode", upgrade_mode));
is_upgrade_mode = true;
} catch (...) {
archive.setNextName(nullptr);
}
if (!is_upgrade_mode) {
try {
archive(cereal::make_nvp("Update mode", upgrade_mode));
} catch (...) {
archive.setNextName(nullptr);
}
}
archive(cereal::make_nvp("Fog address", fog_address));
archive(cereal::make_nvp("Registration status", registration_status));
archive(cereal::make_nvp("Registration details", registration_details));
archive(cereal::make_nvp("Agent ID", agent_id));
archive(cereal::make_nvp("Profile ID", profile_id));
archive(cereal::make_nvp("Tenant ID", tenant_id));
archive(cereal::make_nvp("Manifest status", manifest_status));
archive(cereal::make_nvp("Service policy", service_policies));
archive(cereal::make_nvp("Service settings", service_settings));
}
private:
string last_update_time;
string last_update_status;
string last_update_attempt;
string last_manifest_update;
string policy_version;
string last_policy_update;
string last_settings_update;
string upgrade_mode;
string fog_address;
string registration_status;
string manifest_status;
string manifest_error;
string agent_id;
string profile_id;
string tenant_id;
RegistrationDetails registration_details;
map<string, string> service_policies;
map<string, string> service_settings;
};
class OrchestrationStatus::Impl : Singleton::Provide<I_OrchestrationStatus>::From<OrchestrationStatus>
{
public:
void
writeStatusToFile()
{
auto orchestrations_status_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/orchestrations_status.json",
"orchestration",
"Orchestration status path"
);
auto write_result =
orchestration_tools->objectToJsonFile<Status>(status, orchestrations_status_path);
if (!write_result) {
dbgWarning(D_ORCHESTRATOR) << "Failed to write Orchestration status. File: " << orchestrations_status_path;
}
dbgTrace(D_ORCHESTRATOR) << "Orchestration status file has been updated. File: " << orchestrations_status_path;
}
void
recoverFields() override
{
status.recoverFields();
}
void
setServiceConfiguration(
const string &service_name,
const string &path,
const OrchestrationStatusConfigType &configuration_file_type
)
{
if (shouldPolicyStatusBeIgnored(service_name, path)) return;
switch (configuration_file_type) {
case OrchestrationStatusConfigType::POLICY:
status.insertServicePolicy(service_name, path);
return;
case OrchestrationStatusConfigType::SETTINGS:
status.insertServiceSetting(service_name, path);
return;
case OrchestrationStatusConfigType::MANIFEST:
dbgAssert(false) << "Manifest is not a service configuration file type";
break;
case OrchestrationStatusConfigType::DATA:
return;
case OrchestrationStatusConfigType::COUNT:
break;
}
dbgAssert(false) << "Unknown configuration file type";
}
void
init()
{
time = Singleton::Consume<I_TimeGet>::by<OrchestrationStatus>();
orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<OrchestrationStatus>();
initValues();
loadFromFile();
filesystem_prefix = getFilesystemPathConfig();
dbgTrace(D_ORCHESTRATOR)
<< "Initializing Orchestration status, file system path prefix: "
<< filesystem_prefix;
map<string, string> service_policies_copy = status.getServicePolicies();
for (const pair<string, string> &policy: service_policies_copy) {
setServiceConfiguration(policy.first, policy.second, OrchestrationStatusConfigType::POLICY);
}
auto mainloop = Singleton::Consume<I_MainLoop>::by<OrchestrationStatus>();
mainloop->addRecurringRoutine(
I_MainLoop::RoutineType::Timer,
seconds(5),
[this] ()
{
dbgTrace(D_ORCHESTRATOR) << "Write Orchestration status file <co-routine>";
writeStatusToFile();
},
"Write Orchestration status file"
);
}
private:
void initValues();
bool shouldPolicyStatusBeIgnored(const string &service_name, const string &path);
void
loadFromFile()
{
auto orchestrations_status_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/orchestrations_status.json",
"orchestration",
"Orchestration status path"
);
Maybe<Status> maybe_status_file =
orchestration_tools->jsonFileToObject<Status>(orchestrations_status_path);
if (!maybe_status_file.ok()) {
dbgTrace(D_ORCHESTRATOR)
<< "Failed to load Orchestration status, start with clear status."
<< " Error: " << maybe_status_file.getErr();
return;
}
status = maybe_status_file.unpack();
dbgInfo(D_ORCHESTRATOR) << "Orchestration status loaded from file." << " File: " << orchestrations_status_path;
}
const string & getLastUpdateAttempt() const override { return status.getLastUpdateAttempt(); }
const string & getUpdateStatus() const override { return status.getUpdateStatus(); }
const string & getUpdateTime() const override { return status.getUpdateTime(); }
const string & getLastManifestUpdate() const override { return status.getLastManifestUpdate(); }
const string & getPolicyVersion() const override { return status.getPolicyVersion(); }
const string & getLastPolicyUpdate() const override { return status.getLastPolicyUpdate(); }
const string & getLastSettingsUpdate() const override { return status.getLastSettingsUpdate(); }
const string & getUpgradeMode() const override { return status.getUpgradeMode(); }
const string & getFogAddress() const override { return status.getFogAddress(); }
const string & getRegistrationStatus() const override { return status.getRegistrationStatus(); }
const string & getAgentId() const override { return status.getAgentId(); }
const string & getProfileId() const override { return status.getProfileId(); }
const string & getTenantId() const override { return status.getTenantId(); }
const string & getManifestStatus() const override { return status.getManifestStatus(); }
const string & getManifestError() const override { return status.getManifestError(); }
const string getRegistrationDetails() const override { return status.getRegistrationDetails().toString(); }
const map<string, string> & getServicePolicies() const override { return status.getServicePolicies(); }
const map<string, string> & getServiceSettings() const override { return status.getServiceSettings(); }
void
setIsConfigurationUpdated(EnumArray<OrchestrationStatusConfigType, bool> config_types) override
{
status.setIsConfigurationUpdated(config_types, time->getLocalTimeStr());
}
void
setPolicyVersion(const string &_policy_version) override
{
status.setPolicyVersion(_policy_version);
}
void
setRegistrationStatus(const string &_reg_status) override
{
status.setRegistrationStatus(_reg_status);
}
void
setUpgradeMode(const string &_upgrade_mode) override
{
status.setUpgradeMode(_upgrade_mode);
}
void
setAgentType(const string &_agent_type) override
{
status.setAgentType(_agent_type);
}
void
setAgentDetails(
const string &_agent_id,
const string &_profile_id,
const string &_tenant_id) override
{
status.setAgentDetails(_agent_id, _profile_id, _tenant_id);
}
void
setLastUpdateAttempt() override
{
status.setLastUpdateAttempt(time->getLocalTimeStr());
}
void
setFogAddress(const string &_fog_address) override
{
status.setFogAddress(_fog_address);
}
void
setFieldStatus(
const OrchestrationStatusFieldType &field_type_status,
const OrchestrationStatusResult &status_result,
const string &failure_reason) override
{
string field_value = status_string_map.at(status_result) + " " + failure_reason;
switch (field_type_status) {
case OrchestrationStatusFieldType::REGISTRATION:
status.setRegistrationStatus(field_value);
return;
case OrchestrationStatusFieldType::MANIFEST:
status.setManifestStatus(field_value);
status.setManifestError(failure_reason);
return;
case OrchestrationStatusFieldType::LAST_UPDATE:
if (status_result == OrchestrationStatusResult::SUCCESS) {
status.setLastUpdateTime(time->getLocalTimeStr());
}
if (status.getUpdateStatus() != field_value) {
writeStatusToFile();
}
status.setLastUpdateStatus(field_value);
return;
case OrchestrationStatusFieldType::COUNT:
break;
}
}
void
setRegistrationDetails(
const string &name,
const string &type,
const string &platform,
const string &arch) override
{
status.setRegistrationDetails(name, type, platform, arch);
}
OrchestrationStatus::Impl & operator=(OrchestrationStatus::Impl &&from) = default;
OrchestrationStatus::Impl & operator=(const OrchestrationStatus::Impl &from) = default;
const map<OrchestrationStatusResult, string> status_string_map = {
{ OrchestrationStatusResult::SUCCESS, "Succeeded" },
{ OrchestrationStatusResult::FAILED, "Failed. Reason:" }
};
Status status;
I_TimeGet *time;
I_OrchestrationTools *orchestration_tools;
string filesystem_prefix;
};
void
OrchestrationStatus::Impl::initValues()
{
status.initValues();
}
bool
OrchestrationStatus::Impl::shouldPolicyStatusBeIgnored(
const string &service_name,
const string &path)
{
vector<string> default_status_ingored_policies = {
"rules",
"zones",
"triggers",
"parameters",
"orchestration",
"webUserResponse",
"kubernetescalico",
"activeContextConfig"
};
auto status_ingored_policies = getSettingWithDefault<vector<string>>(
default_status_ingored_policies,
"orchestration",
"Orchestration status ignored policies"
);
auto config_content = orchestration_tools->readFile(path);
if (!config_content.ok() || config_content.unpack().empty()) {
dbgDebug(D_ORCHESTRATOR) << "Can not read the policy for " << service_name;
return true;
}
auto find_exist_iterator = status.getServicePolicies().find(service_name);
auto find_ignored_iterator = find(status_ingored_policies.begin(), status_ingored_policies.end(), service_name);
if (config_content.unpack() == "{}") {
dbgDebug(D_ORCHESTRATOR) << "Skipping status print for an empty policy file. Policy name: " << service_name;
if (find_exist_iterator != status.getServicePolicies().end()) {
status.eraseServicePolicy(service_name);
}
return true;
} else if (find_ignored_iterator != status_ingored_policies.end()) {
dbgDebug(D_ORCHESTRATOR)
<< "Skipping status print for the policy from a list of ignored policies. Policy name: "
<< service_name;
if (find_exist_iterator != status.getServicePolicies().end()) {
status.eraseServicePolicy(service_name);
}
return true;
}
return false;
}
void
OrchestrationStatus::init() { pimpl->init(); }
OrchestrationStatus::OrchestrationStatus() : Component("OrchestrationStatus"), pimpl(make_unique<Impl>()) {}
OrchestrationStatus::~OrchestrationStatus() {}
SASAL_END

View File

@@ -0,0 +1,133 @@
// 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 "package.h"
#include <map>
#include "sasal.h"
SASAL_START // Orchestration - Modules
using namespace std;
using namespace cereal;
const map<string, Package::ChecksumTypes> checksumMap = {
{ "sha1sum", Package::ChecksumTypes::SHA1 },
{ "sha256sum", Package::ChecksumTypes::SHA256 },
{ "sha512sum", Package::ChecksumTypes::SHA512 },
{ "md5sum", Package::ChecksumTypes::MD5 },
};
const map<string, Package::PackageType> packageTypeMap = {
{ "service", Package::PackageType::Service },
{ "shared objects", Package::PackageType::SharedObject },
};
bool
Package::operator==(const Package &other) const
{
return checksum_type == other.getChecksumType() && checksum_value == other.getChecksum();
}
bool
Package::operator!=(const Package &other) const
{
return !((*this) == other);
}
void
Package::serialize(JSONOutputArchive & out_archive) const
{
string type = mapTypeToString<PackageType>(package_type, packageTypeMap);
string checksum_type_as_string = mapTypeToString<ChecksumTypes>(checksum_type, checksumMap);
out_archive(make_nvp("download-path", download_path));
out_archive(make_nvp("relative-path", relative_path));
out_archive(make_nvp("version", version));
out_archive(make_nvp("name", name));
out_archive(make_nvp("checksum-type", checksum_type_as_string));
out_archive(make_nvp("checksum", checksum_value));
out_archive(make_nvp("package-type", type));
if (require_packages.size() > 0) {
out_archive(make_nvp("require", require_packages));
}
if (!installable.ok()) {
out_archive(make_nvp("status", installable.ok()));
out_archive(make_nvp("message", installable.getErr()));
}
}
void
Package::serialize(JSONInputArchive & in_archive)
{
string type;
string checksum_type_as_string;
in_archive(make_nvp("download-path", download_path));
in_archive(make_nvp("version", version));
in_archive(make_nvp("name", name));
in_archive(make_nvp("checksum-type", checksum_type_as_string));
in_archive(make_nvp("checksum", checksum_value));
in_archive(make_nvp("package-type", type));
try {
in_archive(make_nvp("relative-path", relative_path));
} catch (...) {
in_archive.setNextName(nullptr);
}
try {
in_archive(make_nvp("require", require_packages));
} catch (...) {
in_archive.setNextName(nullptr);
}
bool is_installable = true;
try {
in_archive(make_nvp("status", is_installable));
} catch (...) {
in_archive.setNextName(nullptr);
}
if (!is_installable) {
string error_message;
try {
in_archive(make_nvp("message", error_message));
} catch (...) {
in_archive.setNextName(nullptr);
}
installable = genError(error_message);
}
for (auto &character : name) {
// Name Validation: should include only: decimal digit / letter / '.' / '_' / '-'
if (!isalnum(character) && character != '.' && character != '_' && character != '-') {
throw Exception(name + " is invalid package name");
}
}
auto checksum_type_value = checksumMap.find(checksum_type_as_string);
if (checksum_type_value == checksumMap.end()) {
throw Exception(checksum_type_as_string + " isn't a valid checksum type at " + name);
}
checksum_type = checksum_type_value->second;
auto package_type_value = packageTypeMap.find(type);
if (package_type_value == packageTypeMap.end()) {
throw Exception(checksum_type_as_string + " isn't a valid package type at " + name);
}
package_type = package_type_value->second;
}
SASAL_END

View File

@@ -0,0 +1,149 @@
// 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 "url_parser.h"
#include <sstream>
#include "singleton.h"
#include "common.h"
#include "maybe_res.h"
#include "sasal.h"
using namespace std;
SASAL_START // Orchestration - Modules
USE_DEBUG_FLAG(D_ORCHESTRATOR);
ostream &
operator<<(ostream &os, const URLParser &url)
{
return os << url.toString();
}
ostream &
operator<<(ostream &os, const URLProtocol &protocol)
{
switch(protocol) {
case URLProtocol::HTTP: {
return os << "http://";
}
case URLProtocol::HTTPS: {
return os << "https://";
}
case URLProtocol::LOCAL_FILE: {
return os << "file://";
}
default: {
dbgAssert(false) << "Unsupported protocol " << static_cast<unsigned int>(protocol);
return os;
}
}
}
URLParser::URLParser(const string &url)
{
parseURL(url);
}
Maybe<string>
URLParser::getBaseURL() const
{
if (base_url.empty()) return genError("Error: URL not found");
return base_url;
}
void
URLParser::parseURL(const string &url)
{
string url_builder;
protocol = parseProtocol(url);
switch(protocol) {
case URLProtocol::HTTP: {
dbgDebug(D_ORCHESTRATOR) << "Protocol of " << url << " is HTTP";
port = "80";
over_ssl = false;
url_builder = url.substr(7);
break;
}
case URLProtocol::HTTPS: {
dbgDebug(D_ORCHESTRATOR) << "Protocol of " << url << " is HTTPS";
if (url.find("https://") != string::npos) {
url_builder = url.substr(8);
} else {
url_builder = url;
}
port = "443";
over_ssl = true;
break;
}
case URLProtocol::LOCAL_FILE: {
dbgDebug(D_ORCHESTRATOR) << "Protocol of " << url << " is local file.";
base_url = url.substr(7);
return;
}
default: {
dbgAssert(false) << "URL protocol is not supported. Protocol: " << static_cast<unsigned int>(protocol);
return;
}
}
size_t link_extension_position = url_builder.find_first_of("/");
if (link_extension_position != string::npos) {
query = url_builder.substr(link_extension_position);
url_builder = url_builder.substr(0, link_extension_position);
}
size_t port_position = url_builder.find_last_of(":");
string link = url_builder;
if (port_position != string::npos) {
link = url_builder.substr(0, port_position);
port = url_builder.substr(port_position + 1);
}
if (!link.empty()) base_url = link;
if (!query.empty() && query.back() == '/') query.pop_back();
}
URLProtocol
URLParser::parseProtocol(const string &url) const
{
if (url.find("http://") != string::npos) {
return URLProtocol::HTTP;
} else if (url.find("https://") != string::npos) {
return URLProtocol::HTTPS;
} else if (url.find("file://") != string::npos){
return URLProtocol::LOCAL_FILE;
}
dbgWarning(D_ORCHESTRATOR)
<< "No supported protocol in URL, HTTPS default value is used. URL: " << url;
return URLProtocol::HTTPS;
}
void
URLParser::setQuery(const string &new_query)
{
query = new_query;
}
string
URLParser::toString() const
{
stringstream s_build;
s_build << protocol << base_url << query << ":" << port;
return s_build.str();
}
SASAL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
ADD_DEFINITIONS(-Wno-deprecated-declarations)
add_library(orchestration_tools orchestration_tools.cc)
add_subdirectory(orchestration_tools_ut)

View File

@@ -0,0 +1,475 @@
// 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 "orchestration_tools.h"
#include "openssl/md5.h"
#include "openssl/sha.h"
#include "cereal/external/rapidjson/document.h"
#include "cereal/types/vector.hpp"
#include "cereal/types/set.hpp"
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include "sasal.h"
SASAL_START // Orchestration - Tools
using namespace std;
using namespace rapidjson;
static const string base64_base_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
class OrchestrationTools::Impl : Singleton::Provide<I_OrchestrationTools>::From<OrchestrationTools>
{
public:
bool packagesToJsonFile(const map<packageName, Package> &packages, const string &path) const override;
Maybe<map<packageName, Package>> loadPackagesFromJson(const string &path) const override;
Maybe<map<packageName, packageDetails>>
jsonObjectSplitter(
const string &json,
const string &tenant_id) const override;
Maybe<string> readFile(const string &path) const override;
bool writeFile(const string &text, const string &path) const override;
bool removeFile(const string &path) const override;
bool copyFile(const string &src_path, const string &dst_path) const override;
bool doesFileExist(const string &file_path) const override;
bool createDirectory(const string &directory_path) const override;
bool doesDirectoryExist(const string &dir_path) const override;
bool executeCmd(const string &cmd) const override;
bool isNonEmptyFile(const string &path) const override;
Maybe<string> calculateChecksum(Package::ChecksumTypes checksum_type, const string &path) const override;
string base64Encode(const string &input) const override;
string base64Decode(const string &input) const override;
private:
string calculateFileMd5(ifstream &file) const;
string calculateSHA256Sum(ifstream &file) const;
string calculateSHA1Sum(ifstream &file) const;
string calculateSHA512Sum(ifstream &file) const;
};
using packageName = I_OrchestrationTools::packageName;
using packageDetails = I_OrchestrationTools::packageDetails;
static bool
checkExistence(const string &path, bool is_dir)
{
try {
struct stat info;
if (stat(path.c_str(), &info) != 0) return false;
int flag = is_dir ? S_IFDIR : S_IFREG;
return info.st_mode & flag;
} catch (exception &e) {
return false;
}
}
bool
OrchestrationTools::Impl::doesFileExist(const string &file_path) const
{
return checkExistence(file_path, false);
}
bool
OrchestrationTools::Impl::doesDirectoryExist(const string &dir_path) const
{
return checkExistence(dir_path, true);
}
bool
OrchestrationTools::Impl::writeFile(const string &text, const string &path) const
{
dbgDebug(D_ORCHESTRATOR) << "Writing file: text = " << text << ", path = " << path;
if (path.find('/') != string::npos) {
string dir_path = path.substr(0, path.find_last_of('/'));
if (!createDirectory(dir_path)) {
dbgDebug(D_ORCHESTRATOR) << "Failed to write file because directory creation failed. file: "
<< path;
return false;
}
}
try {
ofstream fout(path);
fout << text;
return true;
} catch (const ofstream::failure &e) {
dbgDebug(D_ORCHESTRATOR) << "Error while writing file in " << path << ", " << e.what();
}
return false;
}
bool
OrchestrationTools::Impl::isNonEmptyFile(const string &path) const
{
if (!doesFileExist(path)) {
dbgDebug(D_ORCHESTRATOR) << "Cannot read file, file does not exist. File: " << path;
return false;
}
try {
ifstream text_file(path);
if (!text_file) {
dbgDebug(D_ORCHESTRATOR) << "Cannot open file. File: " << path;
return false;
}
char buf[1];
text_file.read(buf, 1);
return text_file.gcount() != 0;
} catch (const ifstream::failure &e) {
dbgDebug(D_ORCHESTRATOR) << "Error while reading file " << path << ", " << e.what();
}
return false;
}
Maybe<string>
OrchestrationTools::Impl::readFile(const string &path) const
{
if (!doesFileExist(path)) {
dbgDebug(D_ORCHESTRATOR) << "Cannot read file, file does not exist. File: " << path;
return genError("File " + path + " does not exist.");
}
try {
ifstream text_file(path);
if (!text_file) {
return genError("Cannot open file. File: " + path);
}
stringstream buffer;
buffer << text_file.rdbuf();
return buffer.str();
} catch (const ifstream::failure &e) {
dbgDebug(D_ORCHESTRATOR) << "Error while reading file " << path << ", " << e.what();
return genError("Error while reading file " + path + ", " + e.what());
}
}
bool
OrchestrationTools::Impl::removeFile(const string &path) const
{
if (remove(path.c_str()) != 0) {
dbgDebug(D_ORCHESTRATOR) << "Error deleting file. File: " << path;
return false;
} else {
dbgDebug(D_ORCHESTRATOR) << "Successfully deleted the file " << path;
}
return true;
}
Maybe<string>
OrchestrationTools::Impl::calculateChecksum(Package::ChecksumTypes checksum_type, const string &path) const
{
if (!doesFileExist(path)) {
dbgDebug(D_ORCHESTRATOR) << "Cannot read file, file does not exist. File: " << path;
return genError("File " + path + " does not exist.");
}
try {
ifstream file(path);
if (!file) {
return genError("Cannot open file. File: " + path);
}
switch (checksum_type) {
case Package::ChecksumTypes::MD5:
return calculateFileMd5(file);
case Package::ChecksumTypes::SHA256:
return calculateSHA256Sum(file);
case Package::ChecksumTypes::SHA1:
return calculateSHA1Sum(file);
case Package::ChecksumTypes::SHA512:
return calculateSHA512Sum(file);
}
} catch (const ifstream::failure &e) {
dbgDebug(D_ORCHESTRATOR) << "Error while reading file " << path << ", " << e.what();
return genError("Error while reading file " + path + ", " + e.what());
}
dbgAssert(false) << "Checksum type is not supported. Checksum type: " << static_cast<unsigned int>(checksum_type);
return genError("Unsupported checksum type");
}
bool
OrchestrationTools::Impl::copyFile(const string &src_path, const string &dst_path) const
{
if (!doesFileExist(src_path)) {
dbgDebug(D_ORCHESTRATOR) << "Failed to copy file. File does not exist: " << src_path;
return false;
}
if (src_path.compare(dst_path) == 0) {
dbgDebug(D_ORCHESTRATOR) << "Source path is equal to the destination path. Path: " << src_path;
return true;
}
if (dst_path.find('/') != string::npos) {
string dir_path = dst_path.substr(0, dst_path.find_last_of('/'));
if (!createDirectory(dir_path)) {
dbgDebug(D_ORCHESTRATOR) << "Failed to copy file. Directory creation failed: " << dir_path;
return false;
}
}
try {
ifstream src(src_path, ios::binary);
ofstream dest(dst_path, ios::binary);
dest << src.rdbuf();
return true;
} catch (const ios_base::failure &e) {
dbgDebug(D_ORCHESTRATOR) << "Failed to copy file " << src_path << " to " << dst_path << ", " << e.what();
}
return false;
}
Maybe<map<packageName, packageDetails>>
OrchestrationTools::Impl::jsonObjectSplitter(const string &json, const string &tenant_id) const
{
Document document;
map<string, string> parsed;
document.Parse(json.c_str());
if (document.HasParseError()) return genError("JSON file is not valid.");
for (Value::MemberIterator itr = document.MemberBegin(); itr != document.MemberEnd(); ++itr) {
if (!tenant_id.empty() && itr->value.IsObject()) {
itr->value.AddMember(
Value("tenantID"),
Value(tenant_id.c_str(), tenant_id.size()),
document.GetAllocator()
);
}
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
itr->value.Accept(writer);
parsed.insert({itr->name.GetString(), buffer.GetString()});
}
return parsed;
}
bool
OrchestrationTools::Impl::packagesToJsonFile(const map<packageName, Package> &packages, const string &path) const
{
try {
ofstream os(path);
cereal::JSONOutputArchive archive_out(os);
vector<Package> packges_vector;
for (auto p: packages) {
packges_vector.push_back(p.second);
}
archive_out(cereal::make_nvp("packages", packges_vector));
} catch (cereal::Exception &e) {
dbgDebug(D_ORCHESTRATOR) << "Failed to write vector of packages to JSON file " << path << ", " << e.what();
return false;
}
return true;
}
Maybe<map<packageName, Package>>
OrchestrationTools::Impl::loadPackagesFromJson(const string &path) const
{
dbgDebug(D_ORCHESTRATOR) << "Parsing packages from " << path;
try {
ifstream is(path);
cereal::JSONInputArchive archive_in(is);
vector<Package> packages_vector;
archive_in(packages_vector);
map<packageName, Package> packages;
for (auto p: packages_vector) {
packages[p.getName()] = p;
}
return packages;
} catch (const exception &e) {
dbgDebug(D_ORCHESTRATOR) << "Failed to load vector of packages from JSON file " << path << ", " << e.what();
return genError(e.what());
}
}
bool
OrchestrationTools::Impl::createDirectory(const string &directory_path) const
{
string dir;
struct stat info;
for (size_t i = 0; i < directory_path.size(); i++) {
dir.push_back(directory_path[i]);
if (directory_path[i] == '/' || i + 1 == directory_path.size()) {
if (stat(dir.c_str(), &info) != 0) {
if(mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
dbgDebug(D_ORCHESTRATOR) << "Failed to create directory " << directory_path;
return false;
}
}
}
}
return true;
}
bool
OrchestrationTools::Impl::executeCmd(const string &cmd) const
{
int ret = system(cmd.c_str());
if (ret != 0) {
dbgDebug(D_ORCHESTRATOR) << "System command failed, " + cmd;
return false;
}
return true;
}
string
OrchestrationTools::Impl::calculateFileMd5(ifstream &file) const
{
MD5_CTX md5_Context;
MD5_Init(&md5_Context);
char read_buf[512];
while (file) {
file.read(read_buf, 512);
auto size = file.gcount();
if (!size) break;
MD5_Update(&md5_Context, read_buf, size);
}
unsigned char digest[16];
MD5_Final(digest, &md5_Context);
stringstream out;
for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
out << setfill('0') << setw(2) << hex << (unsigned int)digest[i];
}
return out.str();
}
string
OrchestrationTools::Impl::calculateSHA256Sum(ifstream &file) const
{
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
char read_buf[512];
while (file) {
file.read(read_buf, 512);
auto size = file.gcount();
if (!size) break;
SHA256_Update(&sha256, read_buf, size);
}
SHA256_Final(hash, &sha256);
stringstream string_stream;
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
string_stream << hex << setw(2) << setfill('0') << (int)hash[i];
}
return string_stream.str();
}
string
OrchestrationTools::Impl::calculateSHA1Sum(ifstream &file) const
{
unsigned char hash[SHA_DIGEST_LENGTH];
SHA_CTX sha1;
SHA1_Init(&sha1);
char read_buf[512];
while (file) {
file.read(read_buf, 512);
auto size = file.gcount();
if (!size) break;
SHA1_Update(&sha1, read_buf, size);
}
SHA1_Final(hash, &sha1);
stringstream string_stream;
for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
string_stream << hex << setw(2) << setfill('0') << (int)hash[i];
}
return string_stream.str();
}
string
OrchestrationTools::Impl::calculateSHA512Sum(ifstream &file) const
{
unsigned char hash[SHA512_DIGEST_LENGTH];
SHA512_CTX sha512;
SHA512_Init(&sha512);
char read_buf[512];
while (file) {
file.read(read_buf, 512);
auto size = file.gcount();
if (!size) break;
SHA512_Update(&sha512, read_buf, size);
}
SHA512_Final(hash, &sha512);
stringstream string_stream;
for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) {
string_stream << hex << setw(2) << setfill('0') << (int)hash[i];
}
return string_stream.str();
}
string
OrchestrationTools::Impl::base64Encode(const string &input) const
{
string out;
int val = 0, val_base = -6;
for (unsigned char c : input) {
val = (val << 8) + c;
val_base += 8;
while (val_base >= 0) {
out.push_back(base64_base_str[(val >> val_base) & 0x3F]);
val_base -= 6;
}
}
// -6 indicates the number of bits to take from each character
// (6 bits is enough to present a range of 0 to 63)
if (val_base > -6) out.push_back(base64_base_str[((val << 8) >> (val_base + 8)) & 0x3F]);
while (out.size() % 4) out.push_back('=');
return out;
}
string
OrchestrationTools::Impl::base64Decode(const string &input) const
{
string out;
vector<int> T(256, -1);
for (int i = 0; i < 64; i++) {
T[base64_base_str[i]] = i;
}
int val = 0, valb = -8;
for (unsigned char c : input) {
if (T[c] == -1) break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val >> valb) & 0xFF));
valb -= 8;
}
}
return out;
}
OrchestrationTools::OrchestrationTools() : Component("OrchestrationTools"), pimpl(make_unique<Impl>()) {}
OrchestrationTools::~OrchestrationTools() {}
SASAL_END

View File

@@ -0,0 +1,7 @@
link_directories(${ng_module_osrc_openssl_path}/lib)
add_unit_test(
orchestration_tools_ut
"orchestration_tools_ut.cc"
"orchestration_modules;orchestration_tools;singleton;-lcrypto;"
)

View File

@@ -0,0 +1,263 @@
#include "orchestration_tools.h"
#include "cptest.h"
using namespace std;
using namespace testing;
class OrchestrationToolsTest : public Test
{
public:
OrchestrationToolsTest() : manifest_file("manifest.json")
{
}
void
cleanSpaces(string &str)
{
str.erase(remove(str.begin(), str.end(), ' '), str.end());
}
OrchestrationTools orchestration_tools;
I_OrchestrationTools *i_orchestration_tools = Singleton::Consume<I_OrchestrationTools>::from(orchestration_tools);
string manifest_file = "manifest.json";
string manifest_text = "{"
" \"packages\": ["
" {"
" \"download-path\": \"https://a/install_orchestration.sh\","
" \"relative-path\": \"/install_orchestration.sh\","
" \"name\": \"l4_firewall\","
" \"version\": \"b\","
" \"checksum-type\": \"sha1sum\","
" \"checksum\": \"206afe939eb53168d70fbb777afb4e814097c4dc\","
" \"package-type\": \"service\","
" \"require\": []"
" },"
" {"
" \"name\": \"orchestration\","
" \"download-path\": \"https://a/install_orchestration.sh\","
" \"relative-path\": \"/install_orchestration.sh\","
" \"version\": \"c\","
" \"checksum-type\": \"md5sum\","
" \"checksum\": \"04417eef36f93cec4ca7a435bdcd004508dbaa83\","
" \"package-type\": \"service\","
" \"require\": []"
" }"
" ]"
"}";
};
TEST_F(OrchestrationToolsTest, doNothing)
{
}
TEST_F(OrchestrationToolsTest, writeReadTextToFile)
{
EXPECT_TRUE(i_orchestration_tools->writeFile(manifest_text, manifest_file));
EXPECT_TRUE(i_orchestration_tools->doesFileExist(manifest_file));
EXPECT_TRUE(i_orchestration_tools->isNonEmptyFile(manifest_file));
EXPECT_EQ(manifest_text, i_orchestration_tools->readFile(manifest_file).unpack());
EXPECT_FALSE(i_orchestration_tools->isNonEmptyFile("no_such_file"));
}
TEST_F(OrchestrationToolsTest, loadPackagesFromJsonTest)
{
EXPECT_TRUE(i_orchestration_tools->writeFile("blabla", "in_test.json"));
string file_name = "in_test.json";
Maybe<map<string, Package>> packages = i_orchestration_tools->loadPackagesFromJson(file_name);
EXPECT_FALSE(packages.ok());
Maybe<string> value = i_orchestration_tools->readFile(manifest_file);
packages = i_orchestration_tools->loadPackagesFromJson(manifest_file);
EXPECT_TRUE(packages.ok());
EXPECT_EQ(2u, packages.unpack().size());
EXPECT_TRUE(packages.unpack().find("orchestration") != packages.unpack().end());
EXPECT_TRUE(packages.unpack().find("l4_firewall") != packages.unpack().end());
EXPECT_TRUE(packages.unpack().find("Hello World") == packages.unpack().end());
}
TEST_F(OrchestrationToolsTest, copyFile)
{
EXPECT_TRUE(i_orchestration_tools->writeFile("blabla", "in_test.json"));
EXPECT_TRUE(i_orchestration_tools->copyFile("in_test.json", "cpy_test.json"));
EXPECT_EQ("blabla", i_orchestration_tools->readFile("cpy_test.json").unpack());
EXPECT_FALSE(i_orchestration_tools->copyFile("NOT_EXISTS_FILE", "cpy2_test.json"));
auto read_unexists_file = i_orchestration_tools->readFile("cpy2_test.json");
EXPECT_FALSE(read_unexists_file.ok());
EXPECT_THAT(read_unexists_file, IsError("File cpy2_test.json does not exist."));
}
TEST_F(OrchestrationToolsTest, checksumTest)
{
EXPECT_EQ("df5ea29924d39c3be8785734f13169c6",
i_orchestration_tools->calculateChecksum(Package::ChecksumTypes::MD5, "in_test.json").unpack());
EXPECT_EQ("ccadd99b16cd3d200c22d6db45d8b6630ef3d936767127347ec8a76ab992c2ea",
i_orchestration_tools->calculateChecksum(Package::ChecksumTypes::SHA256, "in_test.json").unpack());
EXPECT_EQ("bb21158c733229347bd4e681891e213d94c685be",
i_orchestration_tools->calculateChecksum(Package::ChecksumTypes::SHA1, "in_test.json").unpack());
EXPECT_EQ("d1c2e12cfeababc8b95daf6902e210b170992e68fd1c1f19565a40cf0099c6e2cb559"
"b85d7c14ea05b4dca0a790656d003ccade9286827cffdf8e664fd271499",
i_orchestration_tools->calculateChecksum(Package::ChecksumTypes::SHA512, "in_test.json").unpack());
EXPECT_NE(
"12342",
i_orchestration_tools->calculateChecksum(Package::ChecksumTypes::SHA256, "in_test.json").unpack()
);
}
TEST_F(OrchestrationToolsTest, removeTestFiles)
{
EXPECT_TRUE(i_orchestration_tools->doesFileExist(manifest_file));
EXPECT_TRUE(i_orchestration_tools->removeFile(manifest_file));
EXPECT_FALSE(i_orchestration_tools->doesFileExist(manifest_file));
EXPECT_TRUE(i_orchestration_tools->doesFileExist(string("in_test.json")));
EXPECT_TRUE(i_orchestration_tools->removeFile(string("in_test.json")));
EXPECT_FALSE(i_orchestration_tools->doesFileExist(string("in_test.json")));
EXPECT_TRUE(i_orchestration_tools->doesFileExist(string("cpy_test.json")));
EXPECT_TRUE(i_orchestration_tools->removeFile(string("cpy_test.json")));
EXPECT_FALSE(i_orchestration_tools->doesFileExist(string("cpy_test.json")));
EXPECT_FALSE(i_orchestration_tools->removeFile(string("test.json")));
}
TEST_F(OrchestrationToolsTest, jsonObjectSplitter)
{
string update_text = "{"
" \"manifest\":"
" {"
" \"checksaum\":\"12e307c8f0aab4f51a160d5fb2396de1ca9da5b9\","
" \"download-options\": ["
" \"http://172.23.92.135/manifest_file.txt\""
" ]"
" },"
" \"policy\":"
" {"
" \"checksum\":\"82e307c8f0aab4f51a160d5fb2396de1ca9da5b9\","
" \"download-opations\": ["
" \"http://172.23.92.135/policy_file.txt\","
" \"ftp://172.23.92.135/policy_file.txt\""
" ]"
" },"
" \"version\": \"10\""
"}";
string manifest = "{"
" \"checksaum\":\"12e307c8f0aab4f51a160d5fb2396de1ca9da5b9\","
" \"download-options\": ["
" \"http://172.23.92.135/manifest_file.txt\""
" ]"
" }";
string policy = "{"
" \"checksum\":\"82e307c8f0aab4f51a160d5fb2396de1ca9da5b9\","
" \"download-opations\": ["
" \"http://172.23.92.135/policy_file.txt\","
" \"ftp://172.23.92.135/policy_file.txt\""
" ]"
" }";
Maybe<map<string, string>> parsed = i_orchestration_tools->jsonObjectSplitter(update_text, "");
EXPECT_TRUE(parsed.ok());
cleanSpaces(manifest);
EXPECT_EQ(manifest, parsed.unpack().find("manifest")->second);
cleanSpaces(policy);
EXPECT_EQ(policy, parsed.unpack().find("policy")->second);
string policy_value = parsed.unpack().find("policy")->second;
EXPECT_TRUE(policy_value.find("82e307c8f0aab4f51a160d5fb2396de1ca9da5b9") != string::npos);
string invalid_json = "{"
" \"manifest\":"
" {"
" \"checksaum\":\"12e307c8f0aab4f51a160d5fb2396de1ca9da5b9\","
" \"download-options\": ["
" \"http://172.23.92.135/manifest_file.txt\""
" ]";
parsed = i_orchestration_tools->jsonObjectSplitter(invalid_json, "");
EXPECT_FALSE(parsed.ok());
}
TEST_F(OrchestrationToolsTest, jsonFileToPackages)
{
stringstream string_stream;
string_stream << "{"
" \"packages\": ["
" {"
" \"download-path\": \"https://a/install_orchestration.sh\","
" \"relative-path\": \"/install_orchestration.sh\","
" \"name\": \"nano-agent\","
" \"version\": \"24452\","
" \"checksum-type\": \"sha1sum\","
" \"checksum\": \"a58bbab8020b0e6d08568714b5e582a3adf9c805\","
" \"package-type\": \"service\","
" \"require\": []"
" }"
" ]"
"}";
i_orchestration_tools->writeFile(string_stream.str(), "packages_tmp.json");
Maybe<map<string, Package>> packages = i_orchestration_tools->loadPackagesFromJson("packages_tmp.json");
EXPECT_TRUE(packages.ok());
EXPECT_TRUE(packages.unpack().find("nano-agent") != packages.unpack().end());
}
TEST_F(OrchestrationToolsTest, packagesToJsonFile)
{
stringstream string_stream;
string_stream << "{"
" \"packages\": ["
" {"
" \"download-path\": \"https://a/install_orchestration.sh\","
" \"relative-path\": \"/install_orchestration.sh\","
" \"name\": \"my\","
" \"version\": \"c\","
" \"checksum-type\": \"sha1sum\","
" \"checksum\": \"a58bbab8020b0e6d08568714b5e582a3adf9c805\","
" \"package-type\": \"service\","
" \"require\": []"
" }"
" ]"
"}";
i_orchestration_tools->writeFile(string_stream.str(), "packages.json");
Maybe<map<string, Package>> packages = i_orchestration_tools->loadPackagesFromJson("packages.json");
EXPECT_TRUE(packages.ok());
EXPECT_TRUE(i_orchestration_tools->packagesToJsonFile(packages.unpack(), "packages.json"));
auto file_content = i_orchestration_tools->readFile("packages.json").unpack();
EXPECT_TRUE(file_content.find("a58bbab8020b0e6d08568714b5e582a3adf9c805") != string::npos);
}
TEST_F(OrchestrationToolsTest, executeCommand)
{
EXPECT_TRUE(i_orchestration_tools->executeCmd("exit 0"));
EXPECT_FALSE(i_orchestration_tools->executeCmd("exit 1"));
}
TEST_F(OrchestrationToolsTest, createDirectory)
{
string path = "/tmp/temp_dir";
EXPECT_TRUE(i_orchestration_tools->createDirectory(path));
EXPECT_TRUE(i_orchestration_tools->doesDirectoryExist(path));
// get True after the directory already exists
EXPECT_TRUE(i_orchestration_tools->createDirectory(path));
}
TEST_F(OrchestrationToolsTest, base64DecodeEncode)
{
string clear_text = "{\n"
" \"token\": \"77f380c5-9397-4e53-bb78-7c9df8f80a03\",\n"
" \"expired\": false\n"
"}";
string base64_text = "ewogICAidG9rZW4iOiAiNzdmMzgwYzUtOTM5Ny00ZTUzLWJiNzgtN2M5Z"\
"GY4ZjgwYTAzIiwKICAgImV4cGlyZWQiOiBmYWxzZQp9";
EXPECT_EQ(clear_text, i_orchestration_tools->base64Decode(base64_text));
EXPECT_EQ(base64_text, i_orchestration_tools->base64Encode(clear_text));
string test_str = "";
EXPECT_EQ(test_str, i_orchestration_tools->base64Decode(i_orchestration_tools->base64Encode(test_str)));
test_str = "TEStsr fassaf saf";
EXPECT_EQ(test_str, i_orchestration_tools->base64Decode(i_orchestration_tools->base64Encode(test_str)));
test_str = "T24122142sfsavs!@!%";
EXPECT_EQ(test_str, i_orchestration_tools->base64Decode(i_orchestration_tools->base64Encode(test_str)));
test_str = "\nsdlsakdsad\nsdaslds";
EXPECT_EQ(test_str, i_orchestration_tools->base64Decode(i_orchestration_tools->base64Encode(test_str)));
}

View File

@@ -0,0 +1,15 @@
link_directories(${ng_module_osrc_openssl_path}/lib)
link_directories(${ng_module_osrc_curl_path}/lib)
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
orchestration_ut
"orchestration_ut.cc"
"orchestration;rest;manifest_controller;service_controller;orchestration_downloader;agent_details;package_handler;orchestration_modules;orchestration_tools;environment;config;logging;version;shell_cmd;message;update_communication;agent_details_reporter;connkey;encryptor;metric;ip_utilities;event_is;-lcrypto;-lboost_filesystem;-lboost_regex;-lssl"
)
add_unit_test(
orchestration_multitenant_ut
"orchestration_multitenant_ut.cc"
"orchestration;rest;manifest_controller;service_controller;orchestration_downloader;agent_details;package_handler;orchestration_modules;orchestration_tools;environment;config;logging;version;shell_cmd;message;update_communication;agent_details_reporter;connkey;encryptor;metric;ip_utilities;event_is;-lcrypto;-lboost_filesystem;-lboost_regex;-lssl;curl"
)

View File

@@ -0,0 +1,445 @@
#include "orchestration_comp.h"
#include "cptest.h"
#include "mock/mock_encryptor.h"
#include "mock/mock_orchestration_tools.h"
#include "mock/mock_downloader.h"
#include "mock/mock_manifest_controller.h"
#include "mock/mock_service_controller.h"
#include "mock/mock_orchestration_status.h"
#include "mock/mock_update_communication.h"
#include "mock/mock_details_resolver.h"
#include "mock/mock_agent_details_reporter.h"
#include "mock/mock_logging.h"
#include "mock/mock_shell_cmd.h"
#include "mock/mock_mainloop.h"
#include "mock/mock_messaging.h"
#include "mock/mock_time_get.h"
#include "mock/mock_rest_api.h"
#include "mock/mock_tenant_manager.h"
#include "mock/mock_messaging_downloader.h"
#include "config.h"
#include "config_component.h"
#include "agent_details.h"
using namespace testing;
using namespace std;
class OrchestrationMultitenancyTest : public Test
{
public:
OrchestrationMultitenancyTest() : config(Singleton::Consume<Config::I_Config>::from(config_comp))
{
EXPECT_CALL(
rest,
mockRestCall(RestAction::SET, "new-configuration", _)
).WillOnce(WithArg<2>(Invoke(this, &OrchestrationMultitenancyTest::setNewConfiguration)));
EXPECT_CALL(tenant_manager, getTimeoutVal()).WillOnce(Return(chrono::microseconds(0)));
EXPECT_CALL(
mock_ml,
addRecurringRoutine(I_MainLoop::RoutineType::System, _, _, _, _)
).WillRepeatedly(Return(0));
EXPECT_CALL(
mock_ml,
addOneTimeRoutine(I_MainLoop::RoutineType::System, _, "Configuration update registration", false)
).WillOnce(Return(0));
config_comp.preload();
config_comp.init();
}
void
init()
{
EXPECT_CALL(mock_service_controller, isServiceInstalled("Access Control")).WillRepeatedly(Return(false));
// This Holding the Main Routine of the Orchestration.
EXPECT_CALL(
mock_ml,
addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, _, "Orchestration runner", true)
).WillOnce(DoAll(SaveArg<1>(&routine), Return(1)));
EXPECT_CALL(mock_shell_cmd, getExecOutput("openssl version -d | cut -d\" \" -f2 | cut -d\"\\\"\" -f2", _, _))
.WillOnce(Return(string("OpenSSL certificates Directory")));
EXPECT_CALL(rest, mockRestCall(RestAction::SHOW, "orchestration-status", _)).WillOnce(
WithArg<2>(Invoke(this, &OrchestrationMultitenancyTest::setRestStatus)));
doEncrypt();
orchestration_comp.init();
}
bool
restHandler(const unique_ptr<RestInit> &rest_ptr)
{
rest_handler = rest_ptr->getRest();
return true;
}
void
doEncrypt()
{
Maybe<string> err = genError("No file exist");
EXPECT_CALL(mock_orchestration_tools, readFile("/etc/cp/conf/user-cred.json")).WillOnce(Return(err));
EXPECT_CALL(mock_orchestration_tools, writeFile("This is fake", "/etc/cp/data/data1.a")).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, writeFile("0000 is fake", "/etc/cp/data/data4.a")).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, writeFile("This is 3333", "/etc/cp/data/data6.a")).WillOnce(Return(true));
}
void
expectDetailsResolver()
{
Maybe<tuple<string, string, string>> no_nginx(genError("No nginx"));
EXPECT_CALL(mock_details_resolver, getPlatform()).WillOnce(Return(string("linux")));
EXPECT_CALL(mock_details_resolver, getArch()).WillOnce(Return(string("x86_64")));
EXPECT_CALL(mock_details_resolver, isReverseProxy()).WillOnce(Return(false));
EXPECT_CALL(mock_details_resolver, isKernelVersion3OrHigher()).WillOnce(Return(false));
EXPECT_CALL(mock_details_resolver, isGwNotVsx()).WillOnce(Return(false));
EXPECT_CALL(mock_details_resolver, isVersionEqualOrAboveR8110()).WillOnce(Return(false));
EXPECT_CALL(mock_details_resolver, parseNginxMetadata()).WillOnce(Return(no_nginx));
EXPECT_CALL(mock_details_resolver, getAgentVersion())
.WillOnce(Return("1.1.1"))
.WillOnce(Return("1.1.1"));
map<string, string> resolved_mgmt_details({{"kernel_version", "4.4.0-87-generic"}});
EXPECT_CALL(mock_details_resolver, getResolvedDetails()).WillOnce(Return(resolved_mgmt_details));
}
void
runRoutine()
{
routine();
}
void
preload()
{
orchestration_comp.preload();
}
void
waitForRestCall()
{
EXPECT_CALL(rest, mockRestCall(RestAction::SHOW, "orchestration-status", _)).WillRepeatedly(Return(true));
}
void
performSetNewConfiguration(const string &file_path)
{
stringstream rest_call_parameters;
rest_call_parameters
<< "{\"configuration_file_paths\": ["
<< (file_path == "" ? file_path : (string("\"") + file_path + string("\"")))
<< "] }";
set_new_configuration->performRestCall(rest_call_parameters);
}
bool
declareVariable(const unique_ptr<RestInit> &p)
{
set_new_configuration = p->getRest();
return true;
}
::Environment env;
OrchestrationComp orchestration_comp;
AgentDetails agent_details;
ConfigComponent config_comp;
Config::I_Config *config;
unique_ptr<ServerRest> set_new_configuration;
unique_ptr<ServerRest> rest_status;
unique_ptr<ServerRest> rest_handler;
unique_ptr<ServerRest> declare_variable;
StrictMock<MockMainLoop> mock_ml;
StrictMock<MockEncryptor> mock_encryptor;
StrictMock<MockOrchestrationTools> mock_orchestration_tools;
StrictMock<MockDownloader> mock_downloader;
StrictMock<MockShellCmd> mock_shell_cmd;
StrictMock<MockMessaging> mock_message;
StrictMock<MockRestApi> rest;
StrictMock<MockServiceController> mock_service_controller;
StrictMock<MockManifestController> mock_manifest_controller;
StrictMock<MockUpdateCommunication> mock_update_communication;
StrictMock<MockMessagingDownloader> mock_messaging_downloader;
StrictMock<MockTenantManager> tenant_manager;
NiceMock<MockOrchestrationStatus> mock_status;
NiceMock<MockTimeGet> mock_time_get;
NiceMock<MockDetailsResolver> mock_details_resolver;
NiceMock<MockAgenetDetailsReporter> mock_agent_reporter;
NiceMock<MockLogging> mock_log;
private:
bool
setNewConfiguration(const unique_ptr<RestInit> &p)
{
set_new_configuration = p->getRest();
return true;
}
bool setRestStatus(const unique_ptr<RestInit> &p)
{
rest_status = p->getRest();
return true;
}
I_MainLoop::Routine routine;
I_MainLoop::Routine status_routine;
};
TEST_F(OrchestrationMultitenancyTest, init)
{
}
TEST_F(OrchestrationMultitenancyTest, handle_virtual_resource)
{
string orchestration_policy_file_path = "/etc/cp/conf/orchestration/orchestration.policy";
string manifest_file_path = "/etc/cp/conf/manifest.json";
string setting_file_path = "/etc/cp/conf/settings.json";
string policy_file_path = "/etc/cp/conf/policy.json";
string data_file_path = "/etc/cp/conf/data.json";
string host_address = "1.2.3.5";
string manifest_checksum= "manifest";
string policy_checksum= "policy";
string settings_checksum= "settings";
string data_checksum= "data";
string first_policy_version = "";
string host_url = "https://" + host_address + "/";
EXPECT_CALL(
rest,
mockRestCall(RestAction::ADD, "proxy", _)
).WillOnce(WithArg<2>(Invoke(this, &OrchestrationMultitenancyTest::restHandler)));
waitForRestCall();
init();
expectDetailsResolver();
Maybe<string> response(
string(
"{\n"
" \"fog-address\": \"" + host_url + "\",\n"
" \"agent-type\": \"test\",\n"
" \"pulling-interval\": 25,\n"
" \"error-pulling-interval\": 15\n"
"}"
)
);
EXPECT_CALL(mock_orchestration_tools, doesFileExist(orchestration_policy_file_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, readFile(orchestration_policy_file_path)).WillOnce(Return(response));
EXPECT_CALL(mock_message, setActiveFog(host_address, 443, true, MessageTypeTag::GENERIC)).WillOnce(Return(true));
EXPECT_CALL(mock_update_communication, setAddressExtenesion(""));
EXPECT_CALL(mock_update_communication, authenticateAgent()).WillOnce(Return(Maybe<void>()));
EXPECT_CALL(mock_manifest_controller, loadAfterSelfUpdate()).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(Package::ChecksumTypes::SHA256, manifest_file_path))
.WillOnce(Return(manifest_checksum));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(Package::ChecksumTypes::SHA256, setting_file_path))
.WillOnce(Return(settings_checksum));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(Package::ChecksumTypes::SHA256, policy_file_path))
.WillOnce(Return(policy_checksum));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(Package::ChecksumTypes::SHA256, data_file_path))
.WillOnce(Return(data_checksum));
EXPECT_CALL(mock_service_controller, getPolicyVersion())
.Times(2).WillRepeatedly(ReturnRef(first_policy_version));
vector<string> active_tenants = { "1236", "1235" };
EXPECT_CALL(tenant_manager, fetchActiveTenants()).WillOnce(Return(active_tenants));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(_, "/etc/cp/conf/tenant_1236/policy.json"))
.WillOnce(Return(string("checksum_policy_tenant_1236")));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(_, "/etc/cp/conf/tenant_1235/policy.json"))
.WillOnce(Return(string("checksum_policy_tenant_1235")));
EXPECT_CALL(mock_orchestration_tools, readFile("/etc/cp/conf/tenant_1236/policy.json"))
.WillOnce(Return(string("{}")));
EXPECT_CALL(mock_orchestration_tools, readFile("/etc/cp/conf/tenant_1235/policy.json"))
.WillOnce(Return(string("{}")));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(_, "/etc/cp/conf/tenant_1236_settings.json"))
.WillOnce(Return(string("checksum_settings_tenant_1236")));
EXPECT_CALL(mock_orchestration_tools, calculateChecksum(_, "/etc/cp/conf/tenant_1235_settings.json"))
.WillOnce(Return(string("checksum_settings_tenant_1235")));
EXPECT_CALL(mock_update_communication, getUpdate(_)).WillOnce(
Invoke(
[&](CheckUpdateRequest &req)
{
EXPECT_THAT(req.getPolicy(), IsValue(policy_checksum));
EXPECT_THAT(req.getSettings(), IsValue(settings_checksum));
EXPECT_THAT(req.getManifest(), IsValue(manifest_checksum));
EXPECT_THAT(req.getData(), IsValue(data_checksum));
string update_response =
"{\n"
" \"manifest\": \"\",\n"
" \"policy\": \"\",\n"
" \"settings\": \"\",\n"
" \"data\": \"\",\n"
" \"virtualPolicy\": {\n"
" \"tenants\": [\n"
" {\n"
" \"tenantId\": \"1236\",\n"
" \"checksum\": \"new_checksum_policy_tenant_1236\",\n"
" \"version\": \"1\"\n"
" },\n"
" {\n"
" \"tenantId\": \"1235\",\n"
" \"checksum\": \"new_checksum_policy_tenant_1235\",\n"
" \"version\": \"1\"\n"
" }\n"
" ]\n"
" },\n"
" \"virtualSettings\": {\n"
" \"tenants\": [\n"
" {\n"
" \"tenantId\": \"1236\",\n"
" \"checksum\": \"new_checksum_settings_tenant_1236\",\n"
" \"version\": \"1\"\n"
" },\n"
" {\n"
" \"tenantId\": \"1235\",\n"
" \"checksum\": \"new_checksum_settings_tenant_1235\",\n"
" \"version\": \"1\"\n"
" }\n"
" ]\n"
" }\n"
"}";
EXPECT_TRUE(req.loadJson(update_response));
return Maybe<void>();
}
)
);
GetResourceFile policy_file(GetResourceFile::ResourceFileType::VIRTUAL_POLICY);
policy_file.addTenant("1236", "1", "new_checksum_policy_tenant_1236");
policy_file.addTenant("1235", "1", "new_checksum_policy_tenant_1235");
map<string, string> download_policy_res = {
{ "1236", "/tmp/orchestration_downloads/virtualPolicy_1236.download" },
{ "1235", "/tmp/orchestration_downloads/virtualPolicy_1235.download" }
};
GetResourceFile settings_file(GetResourceFile::ResourceFileType::VIRTUAL_SETTINGS);
settings_file.addTenant("1236", "1", "new_checksum_settings_tenant_1236");
settings_file.addTenant("1235", "1", "new_checksum_settings_tenant_1235");
map<string, string> download_settings_res = {
{ "1236", "/tmp/orchestration_downloads/virtualSettings_1236.download" },
{ "1235", "/tmp/orchestration_downloads/virtualSettings_1235.download" }
};
EXPECT_CALL(
mock_downloader,
downloadVirtualFileFromFog(_, Package::ChecksumTypes::SHA256)
).WillOnce(
WithArg<0>(
Invoke(
[&] (const GetResourceFile &resourse_file)
{
EXPECT_EQ(resourse_file, policy_file);
return download_policy_res;
}
)
)
).WillOnce(
WithArg<0>(
Invoke(
[&] (const GetResourceFile &resourse_file)
{
EXPECT_EQ(resourse_file, settings_file);
return download_settings_res;
}
)
)
);
EXPECT_CALL(
mock_orchestration_tools,
copyFile(
"/tmp/orchestration_downloads/virtualSettings_1236.download",
"/etc/cp/conf/tenant_1236_settings.json"
)
).WillOnce(Return(true));
EXPECT_CALL(
mock_orchestration_tools,
copyFile(
"/tmp/orchestration_downloads/virtualSettings_1235.download",
"/etc/cp/conf/tenant_1235_settings.json"
)
).WillOnce(Return(true));
vector<string> expected_data_types = {};
EXPECT_CALL(
mock_service_controller,
updateServiceConfiguration(
"/etc/cp/conf/policy.json",
"/etc/cp/conf/settings.json",
expected_data_types,
""
)
).WillOnce(Return(true));
EXPECT_CALL(
mock_service_controller,
updateServiceConfiguration(
"/tmp/orchestration_downloads/virtualPolicy_1236.download",
"/etc/cp/conf/tenant_1236_settings.json",
expected_data_types,
"1236"
)
).WillOnce(Return(true));
EXPECT_CALL(
mock_service_controller,
updateServiceConfiguration(
"/tmp/orchestration_downloads/virtualPolicy_1235.download",
"/etc/cp/conf/tenant_1235_settings.json",
expected_data_types,
"1235"
)
).WillOnce(Return(true));
EXPECT_CALL(mock_ml, yield(A<chrono::microseconds>()))
.WillOnce(
Invoke(
[] (chrono::microseconds microseconds)
{
EXPECT_EQ(1000000, microseconds.count());
}
)
)
.WillOnce(
Invoke(
[] (chrono::microseconds microseconds)
{
EXPECT_EQ(25000000, microseconds.count());
throw invalid_argument("stop while loop");
}
)
);
EXPECT_CALL(
mock_shell_cmd,
getExecOutput(_, _, _)
).WillRepeatedly(Return(string("daniel\n1\n")));
try {
runRoutine();
} catch (const invalid_argument& e) {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
add_library(package_handler package_handler.cc)
add_subdirectory(package_handler_ut)

View File

@@ -0,0 +1,508 @@
// 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 "package_handler.h"
#include "config.h"
#include "sasal.h"
#include "i_shell_cmd.h"
#include <sys/stat.h>
#include <vector>
SASAL_START // Orchestration - Updates Control
USE_DEBUG_FLAG(D_ORCHESTRATOR);
using namespace std;
#ifdef smb
static const string InstallEnvPrefix = "TMPDIR=/storage/tmp ";
#else
static const string InstallEnvPrefix = "";
#endif
enum class PackageHandlerActions {
INSTALL,
UNINSTALL,
PREINSTALL,
POSTINSTALL,
UNREGISTER,
GET_VERSION
};
class AdditionalFlagsConfiguration
{
public:
AdditionalFlagsConfiguration() : flags() {}
void
load(cereal::JSONInputArchive &ar)
{
try {
ar(cereal::make_nvp("flags", flags));
} catch (cereal::Exception &) {
ar.setNextName(nullptr);
}
}
const vector<string> & getFlags() const { return flags; }
private:
vector<string> flags;
};
class PackageHandler::Impl : Singleton::Provide<I_PackageHandler>::From<PackageHandler>
{
public:
void
init()
{
filesystem_prefix = getFilesystemPathConfig();
dbgTrace(D_ORCHESTRATOR) << "Initializing Packet handler, file system path prefix: " << filesystem_prefix;
}
bool shouldInstallPackage(const string &package_name, const string &install_file_path) const override;
bool installPackage(const string &package_name, const string &install_file_path, bool restore_mode) const override;
bool
uninstallPackage(
const string &package_name,
const string &package_path,
const string &install_file_path
) const override;
bool preInstallPackage(const string &package_name, const string &install_file_path) const override;
bool postInstallPackage(const string &package_name, const string &install_file_path) const override;
bool updateSavedPackage(const string &package_name, const string &install_file_path) const override;
private:
void
revertPackage(
const string &package_name,
bool restore_mode,
const string &current_installation_file,
const string &backup_installation_file
) const;
bool setExecutionMode(const string &install_file_path) const;
string filesystem_prefix;
};
static string
packageHandlerActionsToString(PackageHandlerActions action)
{
switch(action) {
case PackageHandlerActions::INSTALL: {
string installation_mode = " --install";
auto trusted_ca_directory = getConfiguration<string>("message", "Trusted CA directory");
if (trusted_ca_directory.ok() && !trusted_ca_directory.unpack().empty()) {
installation_mode += " --certs-dir ";
installation_mode += trusted_ca_directory.unpack();
}
AdditionalFlagsConfiguration additional_flags = getConfigurationWithDefault<AdditionalFlagsConfiguration>(
AdditionalFlagsConfiguration(),
"orchestration",
"additional flags"
);
for (const auto &flag : additional_flags.getFlags()) {
installation_mode += " " + flag;
}
return installation_mode;
}
case PackageHandlerActions::UNINSTALL: {
return string(" --uninstall");
}
case PackageHandlerActions::PREINSTALL: {
return string(" --pre_install_test");
}
case PackageHandlerActions::POSTINSTALL: {
return string(" --post_install_test");
}
case PackageHandlerActions::UNREGISTER: {
return string(" --un-register ");
}
case PackageHandlerActions::GET_VERSION: {
return string(" --version");
}
}
dbgAssert(false) << "Package handler action is not supported. Action: " << static_cast<unsigned int>(action);
return string();
}
void
PackageHandler::init()
{
pimpl->init();
}
void
PackageHandler::preload()
{
registerExpectedConfiguration<bool>("orchestration", "Debug mode");
registerExpectedConfiguration<AdditionalFlagsConfiguration>("orchestration", "additional flags");
registerExpectedConfiguration<uint>("orchestration", "Shell command execution time out");
}
bool
PackageHandler::Impl::setExecutionMode(const string &install_file_path) const
{
return (chmod(install_file_path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) == 0);
}
bool
PackageHandler::Impl::shouldInstallPackage(const string &package_name, const string &install_file_path) const
{
string packages_dir = getConfigurationWithDefault<string>(
filesystem_prefix + "/packages",
"orchestration",
"Packages directory"
);
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
string current_installation_file = packages_dir + "/" + package_name + "/" + package_name;
if (!orchestration_tools->doesFileExist(current_installation_file)) {
dbgDebug(D_ORCHESTRATOR) << "Clean installation - package should be installed. Package name: " << package_name;
return true;
}
setExecutionMode(current_installation_file);
setExecutionMode(install_file_path);
dbgDebug(D_ORCHESTRATOR) << "Checking if new and current packages has different versions";
uint timeout = getConfigurationWithDefault<uint>(5000, "orchestration", "Shell command execution time out");
static const string action = packageHandlerActionsToString(PackageHandlerActions::GET_VERSION);
I_ShellCmd *shell_cmd = Singleton::Consume<I_ShellCmd>::by<PackageHandler>();
Maybe<string> current_package_version = shell_cmd->getExecOutput(current_installation_file + action, timeout);
Maybe<string> new_package_version = shell_cmd->getExecOutput(install_file_path + action, timeout);
if (!current_package_version.ok()) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to get version of current package - Upgrade will be executed. Package name: "
<< package_name
<< ", Error: "
<< current_package_version.getErr();
return true;
}
if (!new_package_version.ok()) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to get version of new package - Upgrade will be executed. Package name: "
<< package_name
<< ", Error: "
<< new_package_version.getErr();
return true;
}
bool should_install = current_package_version.unpack() != new_package_version.unpack();
dbgInfo(D_ORCHESTRATOR)
<< "Version for both new and current version successfully extracted. Package name: "
<< package_name
<< ", Current version: "
<< current_package_version.unpack()
<< ", New version: "
<< new_package_version.unpack()
<< ", Should install: "
<< (should_install ? "yes" : "no");
return should_install;
}
bool
PackageHandler::Impl::installPackage(
const string &package_name,
const string &install_file_path,
bool restore_mode = false) const
{
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
if (!orchestration_tools->doesFileExist(install_file_path)) {
dbgWarning(D_ORCHESTRATOR)
<< "Installation file is not valid for update. File path: "
<< install_file_path
<< " , package: "
<< package_name;
return false;
}
string packages_dir = getConfigurationWithDefault<string>(
filesystem_prefix + "/packages",
"orchestration",
"Packages directory"
);
string backup_extension = getConfigurationWithDefault<string>(".bk", "orchestration", "Backup file extension");
string current_installation_file = packages_dir + "/" + package_name + "/" + package_name;
string backup_installation_file = current_installation_file + backup_extension;
if (restore_mode) {
dbgDebug(D_ORCHESTRATOR) << "Installing package: " << package_name << " from backup.";
} else {
dbgDebug(D_ORCHESTRATOR) << "Installing package: " << package_name;
}
dbgDebug(D_ORCHESTRATOR) << "Changing permissions to execute installation file " << install_file_path;
if (!setExecutionMode(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to change permission for the installation file of " << package_name;
return false;
}
dbgDebug(D_ORCHESTRATOR) << "Start running installation file. Package: "
<< package_name
<< ", path: "
<< install_file_path;
auto action = packageHandlerActionsToString(PackageHandlerActions::INSTALL);
bool cmd_result = orchestration_tools->executeCmd(InstallEnvPrefix + install_file_path + action);
if (!cmd_result) {
dbgWarning(D_ORCHESTRATOR) << "Failed installing package: " << package_name;
revertPackage(package_name, restore_mode, current_installation_file, backup_installation_file);
return false;
}
// In restore mode, we should exit to prevent infinite loop
if (restore_mode) return true;
if (
!orchestration_tools->doesFileExist(current_installation_file) &&
!orchestration_tools->copyFile(install_file_path, current_installation_file)
) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to save installation file. File: "
<< install_file_path
<< ". Target path: "
<< current_installation_file;
return false;
}
dbgDebug(D_ORCHESTRATOR) << "Backup installation file to " << backup_installation_file;
if (!orchestration_tools->copyFile(current_installation_file, backup_installation_file)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to backup installation file: " << current_installation_file;
return false;
}
return true;
}
void
PackageHandler::Impl::revertPackage(
const string &package_name,
bool restore_mode,
const string &current_installation_file,
const string &backup_installation_file) const
{
string orch_service_name = getConfigurationWithDefault<string>(
"orchestration",
"orchestration",
"Service name"
);
string packages_dir = getConfigurationWithDefault<string>(
filesystem_prefix + "/packages",
"orchestration",
"Packages directory"
);
if (package_name == orch_service_name) {
string manifest_file_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/manifest.json",
"orchestration",
"Manifest file path"
);
string temp_extension = getConfigurationWithDefault<string>("_temp", "orchestration", "Temp file extension");
string temp_manifest_file(manifest_file_path + temp_extension);
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
orchestration_tools->removeFile(temp_manifest_file);
}
if (restore_mode) return;
// First we try to recover to last running package and then to
// the backup (2 recent versions are kept)
if (!installPackage(package_name, current_installation_file, true)) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to recover from current installation package,"
<< " trying to use backup package. Current package: "
<< current_installation_file;
if (!installPackage(package_name, backup_installation_file, true)) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to recover from backup installation package. Backup package: "
<< backup_installation_file;
} else {
dbgInfo(D_ORCHESTRATOR)
<< "Installation of the backup package succeeded. Backup package: "
<< backup_installation_file;
}
} else {
dbgInfo(D_ORCHESTRATOR)
<< "Installation of the latest package succeeded. Current package: "
<< current_installation_file;
}
}
bool
PackageHandler::Impl::uninstallPackage(
const string &package_name,
const string &package_path,
const string &install_file_path) const
{
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
if (!orchestration_tools->doesFileExist(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Installation file does not exist. File: " << install_file_path;
return false;
}
string watchdog_path = getConfigurationWithDefault<string>(
filesystem_prefix,
"orchestration",
"Default Check Point directory"
) + "/watchdog/cp-nano-watchdog";
auto action = packageHandlerActionsToString(PackageHandlerActions::UNREGISTER);
if (!orchestration_tools->executeCmd(InstallEnvPrefix + watchdog_path + action + package_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to unregister package from watchdog. Package: " << package_name;
return false;
}
if (!setExecutionMode(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to change package permission. Package: " << package_name;
return false;
}
action = packageHandlerActionsToString(PackageHandlerActions::UNINSTALL);
if (!orchestration_tools->executeCmd(InstallEnvPrefix + install_file_path + action)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to uninstall package. Package: " << package_name;
return false;
}
if (!orchestration_tools->removeFile(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to remove installation package files. Package: " << package_name;
}
string backup_ext = getConfigurationWithDefault<string>(
".bk",
"orchestration",
"Backup file extension"
);
if (!orchestration_tools->removeFile(install_file_path + backup_ext)) {
dbgDebug(D_ORCHESTRATOR) << "Failed to remove backup installation package files. Package: " << package_name;
}
dbgInfo(D_ORCHESTRATOR) << "Package was uninstalled successfully. Package: " << package_name;
return true;
}
bool
PackageHandler::Impl::preInstallPackage(const string &package_name, const string &install_file_path) const
{
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
if (!orchestration_tools->doesFileExist(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Installation file does not exist. File: " << install_file_path;
return false;
}
if (!setExecutionMode(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to change package permission. Package: " << package_name;
return false;
}
auto action = packageHandlerActionsToString(PackageHandlerActions::PREINSTALL);
auto cmd_result = orchestration_tools->executeCmd(InstallEnvPrefix + install_file_path + action);
if (!cmd_result) {
dbgWarning(D_ORCHESTRATOR) << "Failed during pre installation test. Package: " << package_name;
return false;
}
dbgInfo(D_ORCHESTRATOR) << "Pre installation test passed successfully. Package: " << package_name;
return true;
}
bool
PackageHandler::Impl::postInstallPackage(const string &package_name, const string &install_file_path) const
{
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
if (!orchestration_tools->doesFileExist(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Installation file does not exist. File: " << install_file_path;
return false;
}
if (!setExecutionMode(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to change package permission. Package: " << package_name;
return false;
}
auto action = packageHandlerActionsToString(PackageHandlerActions::POSTINSTALL);
auto cmd_result = orchestration_tools->executeCmd(InstallEnvPrefix + install_file_path + action);
if (!cmd_result) {
dbgWarning(D_ORCHESTRATOR) << "Failed during post installation test. Package: " << package_name;
string backup_extension = getConfigurationWithDefault<string>(".bk", "orchestration", "Backup file extension");
string packages_dir = getConfigurationWithDefault<string>(
filesystem_prefix + "/packages",
"orchestration",
"Packages directory"
);
string current_installation_file = packages_dir + "/" + package_name + "/" + package_name;
revertPackage(package_name, false, current_installation_file, current_installation_file + backup_extension);
return false;
}
dbgInfo(D_ORCHESTRATOR) << "Post installation test passed successfully. Package: " << package_name;
return true;
}
bool
PackageHandler::Impl::updateSavedPackage(const string &package_name, const string &install_file_path) const
{
string packages_dir = getConfigurationWithDefault<string>(
filesystem_prefix + "/packages",
"orchestration",
"Packages directory"
);
string backup_extension = getConfigurationWithDefault<string>(".bk", "orchestration", "Backup file extension");
string temp_extension = getConfigurationWithDefault<string>("_temp", "orchestration", "Temp file extension");
string current_installation_file = packages_dir + "/" + package_name + "/" + package_name;
string current_installation_file_backup = current_installation_file + backup_extension;
string tmp_backup = current_installation_file_backup + temp_extension;
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<PackageHandler>();
// Step 1 - save current installation file backup to temporary file.
orchestration_tools->copyFile(current_installation_file_backup, tmp_backup);
// Step 2 - save current installation file to the backuop file.
orchestration_tools->copyFile(current_installation_file, current_installation_file_backup);
dbgDebug(D_ORCHESTRATOR) << "Saving the installation file. "
<< "From: " << install_file_path << ", "
<< " To: " << current_installation_file;
// Step 3 - save the new installation file to the saved package.
if (!orchestration_tools->copyFile(install_file_path, current_installation_file)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to save installation file. File: " << install_file_path;
// Step 3.1 - Revet the backup package
orchestration_tools->copyFile(tmp_backup, current_installation_file_backup);
return false;
}
// Step 4 - remove the current package file
if (!orchestration_tools->removeFile(install_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to remove temporary installation file. File: " << install_file_path;
}
// Step 5 - remove the temporary backup file
orchestration_tools->removeFile(tmp_backup);
return true;
}
PackageHandler::PackageHandler() : Component("PackageHandler"), pimpl(make_unique<Impl>()) {}
PackageHandler::~PackageHandler() {}
SASAL_END

View File

@@ -0,0 +1,8 @@
link_directories(${ng_module_osrc_openssl_path}/lib)
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
package_handler_ut
"package_handler_ut.cc"
"package_handler;orchestration_tools;orchestration_modules;singleton;logging;config;metric;event_is;-lcrypto;-lboost_filesystem;-lboost_regex"
)

View File

@@ -0,0 +1,404 @@
#include "package_handler.h"
#include "cptest.h"
#include "config.h"
#include "config_component.h"
#include "mock/mock_orchestration_tools.h"
#include "mock/mock_mainloop.h"
#include "mock/mock_time_get.h"
#include "mock/mock_shell_cmd.h"
#include <boost/filesystem.hpp>
using namespace std;
using namespace testing;
class PackageHandlerTest : public Test
{
public:
PackageHandlerTest()
:
package_dir("/tmp/packages"),
backup_ext(".bk")
{
setConfiguration<string>(package_dir, "orchestration", "Packages directory");
setConfiguration<string>(backup_ext, "orchestration", "Backup file extension");
setConfiguration<string>("/tmp", "orchestration", "Default Check Point directory");
writeFile("#!/bin/bash\necho \"bb\"\nexit 1", "/tmp/bad.sh");
writeFile("#!/bin/bash\necho \"bb\"", "/tmp/packages/good/good");
writeFile("#!/bin/bash\necho \"bb\"", "/tmp/good.sh");
writeFile("#!/bin/bash\necho \"bb\"", "/tmp/packages/a/a");
package_handler.init();
}
~PackageHandlerTest()
{
namespace fs = boost::filesystem;
fs::path path_to_clean(package_dir);
if (fs::is_directory(path_to_clean)) {
for (fs::directory_iterator iter(path_to_clean); iter != fs::directory_iterator(); ++iter) {
fs::remove_all(iter->path());
}
fs::remove_all(package_dir);
}
}
void
preload()
{
package_handler.preload();
}
bool
writeFile(const string &text, const string &path) const
{
if (path.find('/') != string::npos) {
try {
string dir_path = path.substr(0, path.find_last_of('/'));
boost::filesystem::create_directories(dir_path);
} catch (const boost::filesystem::filesystem_error& e) {
return false;
}
}
try {
ofstream fout(path);
fout << text;
return true;
} catch (const boost::filesystem::filesystem_error& e) {
}
return false;
}
string package_dir;
string backup_ext;
::Environment env;
ConfigComponent config;
NiceMock<MockOrchestrationTools> mock_orchestration_tools;
PackageHandler package_handler;
I_PackageHandler *i_package_handler = Singleton::Consume<I_PackageHandler>::from(package_handler);
NiceMock<MockMainLoop> mock_mainloop;
NiceMock<MockTimeGet> mock_timer;
StrictMock<MockShellCmd> mock_shell;
};
TEST_F(PackageHandlerTest, doNothing)
{
}
TEST_F(PackageHandlerTest, registerExpectedConfig)
{
env.preload();
env.init();
preload();
string config_json =
"{\n"
" \"orchestration\": {\n"
" \"Debug mode\": [\n"
" {\n"
" \"value\": true\n"
" }\n"
" ]\n"
" }\n"
"}";
istringstream string_stream(config_json);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(string_stream);
EXPECT_THAT(getConfiguration<bool>("orchestration", "Debug mode"), IsValue(true));
env.fini();
}
TEST_F(PackageHandlerTest, useAdditionalFlags)
{
env.preload();
env.init();
preload();
registerExpectedConfiguration<string>("orchestration", "Packages directory");
registerExpectedConfiguration<string>("orchestration", "Backup file extension");
registerExpectedConfiguration<string>("orchestration", "Default Check Point directory");
string config_json =
"{\n"
" \"orchestration\": {\n"
" \"additional flags\": [\n"
" {\n"
" \"flags\": [\n"
" \"--flag1\",\n"
" \"--flag2\"\n"
" ]\n"
" }\n"
" ],\n"
" \"Packages directory\": [ { \"value\": \"" + package_dir + "\"}],\n"
" \"Backup file extension\": [ { \"value\": \"" + backup_ext + "\"}],\n"
" \"Default Check Point directory\": [ { \"value\": \"/tmp\"}]"
" }\n"
"}";
istringstream string_stream(config_json);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(string_stream);
string script_path = "/tmp/good.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string package_file = package_dir + "/a/a";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, copyFile(package_file, package_file + backup_ext)).WillOnce(Return(true));
string install_command = script_path + " --install --flag1 --flag2";
EXPECT_CALL(mock_orchestration_tools, executeCmd(install_command)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->installPackage("a", script_path, false));
env.fini();
}
TEST_F(PackageHandlerTest, fileNotExist)
{
EXPECT_CALL(mock_orchestration_tools, doesFileExist("test.json")).WillOnce(Return(false));
EXPECT_NE(true, i_package_handler->installPackage("", "test.json", false));
}
TEST_F(PackageHandlerTest, goodInstall)
{
string script_path = "/tmp/good.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string package_file = package_dir + "/a/a";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, copyFile(package_file, package_file + backup_ext)).WillOnce(Return(true));
string command = script_path + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->installPackage("a", script_path, false));
}
TEST_F(PackageHandlerTest, badInstall)
{
string package_name = "a";
string package_file = package_dir + "/" + package_name + "/" + package_name;
string script_path = "/tmp/bad.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file + backup_ext)).WillOnce(Return(false));
string command = script_path + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->installPackage(package_name, script_path, false));
}
TEST_F(PackageHandlerTest, orcInstallErrorWhileCopyCurrent)
{
string script_path = "/tmp/good.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string package_file = package_dir + "/a/a";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, copyFile(package_file, package_file + backup_ext)).WillOnce(Return(false));
string command = script_path + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_FALSE(i_package_handler->installPackage("a", script_path, false));
}
TEST_F(PackageHandlerTest, orcInstallErrorWhileRemovingNew)
{
string script_path = "/tmp/good.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string package_file = package_dir + "/a/a";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, copyFile(package_file, package_file + backup_ext)).WillOnce(Return(true));
string command = script_path + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->installPackage("a", script_path, false));
}
TEST_F(PackageHandlerTest, badInstallAndRecovery)
{
string package_name = "a";
string package_file = package_dir + "/" + package_name + "/" + package_name;
string script_path = "/tmp/bad.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
string command = script_path + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
command = package_file + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_FALSE(i_package_handler->installPackage(package_name, script_path, false));
}
TEST_F(PackageHandlerTest, badOrcInstallAndRecoveryWithDefualValuesChange)
{
setConfiguration<string>("good", "orchestration", "Service name");
string manifest_file_path = getConfigurationWithDefault<string>("/etc/cp/conf/manifest.json",
"orchestration", "Manifest file path");
string temp_ext = getConfigurationWithDefault<string>("_temp", "orchestration", "Temp file extension");
string temp_manifest_file = manifest_file_path + temp_ext;
string package_file = package_dir + "/good/good";
EXPECT_CALL(mock_orchestration_tools, doesFileExist("/tmp/bad.sh")).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
string command = "/tmp/bad.sh --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
command = package_file + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_FALSE(i_package_handler->installPackage("good", "/tmp/bad.sh", false));
}
TEST_F(PackageHandlerTest, shouldInstall)
{
string old_script_path = "/tmp/packages/my-script/my-script";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(old_script_path)).WillOnce(Return(true));
string new_script_path = "/tmp/new-script.sh";
string version_command = " --version";
EXPECT_CALL(mock_shell, getExecOutput(old_script_path + version_command, 5000, _)).WillOnce(Return(string("a")));
EXPECT_CALL(mock_shell, getExecOutput(new_script_path + version_command, 5000, _)).WillOnce(Return(string("b")));
EXPECT_TRUE(i_package_handler->shouldInstallPackage("my-script", new_script_path));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(old_script_path)).WillOnce(Return(false));
EXPECT_TRUE(i_package_handler->shouldInstallPackage("my-script", new_script_path));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(old_script_path)).WillOnce(Return(true));
EXPECT_CALL(
mock_shell,
getExecOutput(old_script_path + version_command, 5000, _)
).WillOnce(Return(Maybe<string>(genError("Failed"))));
EXPECT_CALL(mock_shell, getExecOutput(new_script_path + version_command, 5000, _)).WillOnce(Return(string("a")));
EXPECT_TRUE(i_package_handler->shouldInstallPackage("my-script", new_script_path));
}
TEST_F(PackageHandlerTest, shouldNotInstall)
{
string old_script_path = "/tmp/packages/my-script/my-script";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(old_script_path)).WillOnce(Return(true));
string version_command = " --version";
EXPECT_CALL(mock_shell, getExecOutput(old_script_path + version_command, 5000, _)).WillOnce(Return(string("a")));
string new_script_path = "/tmp/new-script.sh";
EXPECT_CALL(mock_shell, getExecOutput(new_script_path + version_command, 5000, _)).WillOnce(Return(string("a")));
EXPECT_FALSE(i_package_handler->shouldInstallPackage("my-script", new_script_path));
}
TEST_F(PackageHandlerTest, badPreInstall)
{
string script_path = "/tmp/bad.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->preInstallPackage("a", script_path));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string command = script_path + " --pre_install_test";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->preInstallPackage("a", script_path));
}
TEST_F(PackageHandlerTest, goodPreInstall)
{
string script_path = "/tmp/good.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string command = script_path + " --pre_install_test";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->preInstallPackage("a", script_path));
}
TEST_F(PackageHandlerTest, badPostInstall)
{
string script_path = "/tmp/bad.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->postInstallPackage("a", script_path));
string package_file = package_dir + "/a/a";
string command = script_path + " --post_install_test";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(package_file)).WillOnce(Return(true));
command = package_file + " --install";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_FALSE(i_package_handler->postInstallPackage("a", script_path));
}
TEST_F(PackageHandlerTest, goodPostInstall)
{
string script_path = "/tmp/good.sh";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string package_file = package_dir + "/a/a";
string command = script_path + " --post_install_test";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->postInstallPackage("a", script_path));
}
TEST_F(PackageHandlerTest, badUninstall)
{
string script_path = "/tmp/good.sh";
string watchdog_dir = "/tmp/watchdog";
string watchdog_path = watchdog_dir + "/cp-nano-watchdog";
string package_file = package_dir + "/a/a";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->uninstallPackage("a", package_file, script_path));
string command = watchdog_path + " --un-register " + package_file;
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->uninstallPackage("a", package_file, script_path));
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
command = script_path + " --uninstall";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->uninstallPackage("a", package_file, script_path));
}
TEST_F(PackageHandlerTest, goodUninstall)
{
string script_path = "/tmp/good.sh";
string watchdog_dir = "/tmp/watchdog";
string watchdog_path = watchdog_dir + "/cp-nano-watchdog";
string package_file = package_dir + "/a/a";
EXPECT_CALL(mock_orchestration_tools, doesFileExist(script_path)).WillOnce(Return(true));
string command = watchdog_path + " --un-register " + package_file;
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
command = script_path + " --uninstall";
EXPECT_CALL(mock_orchestration_tools, executeCmd(command)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->uninstallPackage("a", package_file, script_path));
}
TEST_F(PackageHandlerTest, badupdateSavedPackage)
{
string script_path = "/tmp/good.sh";
string package_file = package_dir + "/a/a";
string package_file_backup = package_dir + "/a/a.bk";
string package_file_backup_temp = package_dir + "/a/a.bk_temp";
EXPECT_CALL(mock_orchestration_tools,
copyFile(package_file_backup, package_file_backup_temp)).Times(2).WillRepeatedly(Return(false));
EXPECT_CALL(mock_orchestration_tools,
copyFile(package_file, package_file_backup)).Times(2).WillRepeatedly(Return(false));
EXPECT_CALL(mock_orchestration_tools, copyFile(script_path, package_file)).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools,
copyFile(package_file_backup_temp, package_file_backup)).WillOnce(Return(false));
EXPECT_FALSE(i_package_handler->updateSavedPackage("a", script_path));
EXPECT_CALL(mock_orchestration_tools, copyFile(script_path, package_file)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, removeFile(script_path)).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools, removeFile(package_file_backup_temp)).WillOnce(Return(false));
EXPECT_TRUE(i_package_handler->updateSavedPackage("a", script_path));
}
TEST_F(PackageHandlerTest, goodupdateSavedPackage)
{
string script_path = "/tmp/good.sh";
string package_file = package_dir + "/a/a";
string package_file_backup = package_dir + "/a/a.bk";
string package_file_backup_temp = package_dir + "/a/a.bk_temp";
EXPECT_CALL(mock_orchestration_tools,
copyFile(package_file_backup, package_file_backup_temp)).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools, copyFile(package_file, package_file_backup)).WillOnce(Return(false));
EXPECT_CALL(mock_orchestration_tools, copyFile(script_path, package_file)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, removeFile(script_path)).WillOnce(Return(true));
EXPECT_CALL(mock_orchestration_tools, removeFile(package_file_backup_temp)).WillOnce(Return(true));
EXPECT_TRUE(i_package_handler->updateSavedPackage("a", script_path));
}

View File

@@ -0,0 +1,3 @@
add_library(service_controller service_controller.cc)
add_subdirectory(service_controller_ut)

View File

@@ -0,0 +1,937 @@
// 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 "service_controller.h"
#include <cereal/types/unordered_set.hpp>
#include <algorithm>
#include <sstream>
#include <unistd.h>
#include "config.h"
#include "debug.h"
#include "sasal.h"
#include "rest.h"
#include "connkey.h"
#include "i_messaging.h"
#include "common.h"
#include "log_generator.h"
#include "i_orchestration_tools.h"
#include "customized_cereal_map.h"
SASAL_START // Orchestration - Updates Control
using namespace std;
using namespace ReportIS;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
class SendConfigurations : public ClientRest
{
public:
SendConfigurations(int _id, const string &ver) : id(_id), policy_version(ver) {}
BOTH_PARAM(int, id);
S2C_PARAM(bool, error);
S2C_PARAM(bool, finished);
S2C_OPTIONAL_PARAM(string, error_message);
C2S_PARAM(string, policy_version);
};
class ServiceReconfStatusMonitor : Singleton::Consume<I_ServiceController>, public ServerRest
{
public:
void
doCall() override
{
auto service_controller = Singleton::Consume<I_ServiceController>::by<ServiceReconfStatusMonitor>();
if (!finished.get()) {
service_controller->updateReconfStatus(id.get(), ReconfStatus::IN_PROGRESS);
dbgTrace(D_ORCHESTRATOR)
<< "Request for service reconfiguration, with id "
<< id.get()
<< ", is still in progress.";
return;
}
if (error.get()) {
service_controller->updateReconfStatus(id.get(), ReconfStatus::FAILED);
dbgError(D_ORCHESTRATOR)
<< "Request for service reconfiguration, with id "
<< id.get()
<< ", failed to complete."
<< (error_message.isActive() ? " Error: " + error_message.get() : "");
return;
}
service_controller->updateReconfStatus(id.get(), ReconfStatus::SUCCEEDED);
dbgInfo(D_ORCHESTRATOR)
<< "Request for service reconfiguration, with id "
<< id.get()
<< ", successfully accomplished.";
return;
}
private:
C2S_PARAM(int, id);
C2S_PARAM(bool, error);
C2S_PARAM(bool, finished);
C2S_OPTIONAL_PARAM(string, error_message);
};
bool
ServiceDetails::isServiceActive() const
{
stringstream watchdog_status_cmd;
watchdog_status_cmd
<< getFilesystemPathConfig()
<< "/watchdog/cp-nano-watchdog --status --verbose --service "
<< service_name;
if (!service_id.empty() && service_id != service_name) {
string uuid = "";
if (service_id.find("_") != string::npos) {
string fid = service_id.substr(0, service_id.find("_"));
uuid = service_id.substr(service_id.find("_") + 1, service_id.size());
watchdog_status_cmd << " --family " << fid << " --id " << uuid;
} else {
uuid = service_id;
watchdog_status_cmd << " --id " << uuid;
}
}
dbgDebug(D_ORCHESTRATOR)
<< "Executing service status check via watchdog api. Service name: "
<< service_name
<< ", Watchdog command: "
<< watchdog_status_cmd.str();
I_ShellCmd *shell_cmd = Singleton::Consume<I_ShellCmd>::by<ServiceController>();
Maybe<string> service_status = shell_cmd->getExecOutput(watchdog_status_cmd.str());
if (!service_status.ok()) {
dbgWarning(D_ORCHESTRATOR)
<< "Changing service status to inactive after failure to its status from watchdog. Service name: "
<< service_name;
return false;
}
dbgDebug(D_ORCHESTRATOR)
<< "Successfully retrieved service status from watchdog. Service name: "
<< service_name
<< ", Watchdog output: "
<< *service_status;
string status = service_status.unpack();
for_each(status.begin(), status.end(), [](char &c) { c = ::tolower(c); });
bool is_registered = status.find("not-registered") == string::npos && status.find("registered") != string::npos;
bool is_running = status.find("not-running") == string::npos && status.find("running") != string::npos;
dbgInfo(D_ORCHESTRATOR)
<< "Successfully set service status. Service name: "
<< service_name
<< ", Status: "
<< ((is_registered && is_running) ? "active" : "inactive");
return is_registered && is_running;
}
template <typename Archive>
void
ServiceDetails::serialize(Archive &ar)
{
ar(cereal::make_nvp("Service name", service_name));
ar(cereal::make_nvp("Service ID", service_id));
ar(cereal::make_nvp("Service port", service_port));
ar(cereal::make_nvp("Relevant configs", relevant_configs));
}
ReconfStatus
ServiceDetails::sendNewConfigurations(int configuration_id, const string &policy_version)
{
SendConfigurations new_config(configuration_id, policy_version);
I_Messaging *messaging = Singleton::Consume<I_Messaging>::by<ServiceController>();
Flags<MessageConnConfig> conn_flags;
conn_flags.setFlag(MessageConnConfig::ONE_TIME_CONN);
bool res = messaging->sendObject(
new_config,
I_Messaging::Method::POST,
"127.0.0.1",
service_port,
conn_flags,
"/set-new-configuration"
);
if (!res) {
if(!isServiceActive()) {
dbgDebug(D_ORCHESTRATOR) << "Service " << service_name << " is inactive";
return ReconfStatus::INACTIVE;
}
dbgDebug(D_ORCHESTRATOR) << "Service " << service_name << " didn't respond to new configuration request";
return ReconfStatus::FAILED;
}
auto service_details = Singleton::Consume<I_ServiceController>::by<ServiceDetails>();
if (new_config.finished.get()) {
if (!new_config.error.get()) {
service_details->startReconfStatus(new_config.id.get(), ReconfStatus::SUCCEEDED, service_name, service_id);
dbgDebug(D_ORCHESTRATOR) << "Loading service configuration succeeded for service " << service_name;
return ReconfStatus::SUCCEEDED;
} else {
string log_name = "Agent could not update policy to version " +
service_details->getUpdatePolicyVersion() +
". " +
(new_config.error_message.isActive() ? "Additional details: " + new_config.error_message.get() : "");
LogGen(
log_name,
Audience::SECURITY,
Severity::CRITICAL,
Priority::HIGH,
Tags::ORCHESTRATOR
)
<< LogField("ServiceName", service_name)
<< LogField("policyVersion", service_details->getPolicyVersion());
service_details->startReconfStatus(new_config.id.get(), ReconfStatus::FAILED, service_name, service_id);
dbgDebug(D_ORCHESTRATOR)
<< "Loading service configuration failed for service "
<< service_name
<< " with error: "
<< (new_config.error_message.isActive() ? new_config.error_message.get() : "");
return ReconfStatus::FAILED;
}
}
dbgDebug(D_ORCHESTRATOR) << "Loading service configuration is in progress for service: " << service_name;
service_details->startReconfStatus(new_config.id.get(), ReconfStatus::IN_PROGRESS, service_name, service_id);
return ReconfStatus::IN_PROGRESS;
}
void
SetNanoServiceConfig::doCall()
{
dbgFlow(D_ORCHESTRATOR)
<< "Received registration request from service. Service name: "
<< service_name.get()
<< ", service listening port: "
<< service_listening_port.get();
I_ServiceController *i_service_controller = Singleton::Consume<I_ServiceController>::from<ServiceController>();
i_service_controller->registerServiceConfig(
service_name,
service_listening_port,
expected_configurations,
service_id.isActive() ? service_id.get() : service_name.get()
);
status = true;
}
class ServiceController::Impl
:
Singleton::Provide<I_ServiceController>::From<ServiceController>,
Singleton::Consume<I_OrchestrationTools>
{
public:
void init();
bool
updateServiceConfiguration(
const string &new_policy_path,
const string &new_settings_path,
const vector<string> &new_data_files,
const string &tenant_id
) override;
bool isServiceInstalled(const string &service_name) override;
void registerServiceConfig(
const string &service_name,
PortNumber listening_port,
const vector<string> &relevant_configurations,
const string &service_id
) override;
const string & getPolicyVersion() const override;
const string & getUpdatePolicyVersion() const override;
void updateReconfStatus(int id, ReconfStatus status) override;
void startReconfStatus(
int id,
ReconfStatus status,
const string &service_name,
const string &service_id
) override;
private:
void cleanUpVirtualFiles();
void refreshPendingServices();
bool sendSignalForServices(const set<string> &nano_services_to_update, const string &policy_version);
bool updateServiceConfigurationFile(
const string &configuration_name,
const string &configuration_file_path,
const string &new_configuration_path);
ReconfStatus getUpdatedReconfStatus();
Maybe<ServiceDetails> getServiceDetails(const string &service_name);
map<string, PortNumber> getServiceToPortMap();
template<class Archive>
void serializeRegisterServices(Archive &ar) { ar(pending_services); }
void loadRegisteredServicesFromFile();
void writeRegisteredServicesToFile();
int configuration_id = 0;
map<string, ServiceDetails> registered_services;
map<string, ServiceDetails> pending_services;
string policy_version;
string update_policy_version;
string settings_path;
map<int, ReconfStatus> services_reconf_status;
map<int, string> services_reconf_names;
map<int, string> services_reconf_ids;
string filesystem_prefix;
};
class GetServicesPorts : public ServerRest
{
public:
void
doCall()
{
stringstream output;
auto ports_map = Singleton::Consume<I_ServiceController>::from<ServiceController>()->getServiceToPortMap();
for (auto const& entry: ports_map) {
string service = entry.first;
replace(service.begin(), service.end(), ' ', '-');
output << service << ":";
output << entry.second << ",";
}
ports_list = output.str();
}
S2C_PARAM(string, ports_list);
};
Maybe<ServiceDetails>
ServiceController::Impl::getServiceDetails(const string &service_id)
{
auto iter = registered_services.find(service_id);
if (iter != registered_services.end()) return iter->second;
return genError("did not find service details for the provided service name. service id: " + service_id);
}
ReconfStatus
ServiceController::Impl::getUpdatedReconfStatus()
{
ReconfStatus res = ReconfStatus::SUCCEEDED;
for(auto &service_and_reconf_status : services_reconf_status) {
string service_id = services_reconf_ids[service_and_reconf_status.first];
auto maybe_service = getServiceDetails(service_id);
if (!maybe_service.ok()) {
dbgWarning(D_ORCHESTRATOR) << "Unable to get service details. Error: " << maybe_service.getErr();
continue;
}
if (!maybe_service.unpack().isServiceActive()) {
dbgInfo(D_ORCHESTRATOR)
<< "Service is not active, removing from registered services list. Service: "
<< services_reconf_names[service_and_reconf_status.first]
<< "ID: "
<< service_id;
registered_services.erase(service_id);
service_and_reconf_status.second = ReconfStatus::INACTIVE;
writeRegisteredServicesToFile();
continue;
}
if (res < service_and_reconf_status.second) res = service_and_reconf_status.second;
}
return res;
}
void
ServiceController::Impl::init()
{
auto rest = Singleton::Consume<I_RestApi>::by<ServiceController>();
rest->addRestCall<SetNanoServiceConfig>(RestAction::SET, "nano-service-config");
rest->addRestCall<GetServicesPorts>(RestAction::SHOW, "all-service-ports");
rest->addRestCall<ServiceReconfStatusMonitor>(RestAction::SET, "reconf-status");
Singleton::Consume<I_MainLoop>::by<ServiceController>()->addRecurringRoutine(
I_MainLoop::RoutineType::System,
chrono::seconds(
getConfigurationWithDefault<int>(
86400,
"orchestration",
"Cleanup virtual tenant seconds interval"
)
),
[this] () { cleanUpVirtualFiles(); },
"Cleanup virtual tenants"
);
filesystem_prefix = getFilesystemPathConfig();
loadRegisteredServicesFromFile();
}
void
ServiceController::Impl::loadRegisteredServicesFromFile()
{
auto registered_services_file = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/orchestrations_registered_services.json",
"orchestration",
"Orchestration registered services"
);
auto maybe_registered_services_str = Singleton::Consume<I_OrchestrationTools>::by<ServiceController::Impl>()->
readFile(registered_services_file);
if (!maybe_registered_services_str.ok()) {
dbgTrace(D_ORCHESTRATOR)
<< "could not read file. File: "
<< registered_services_file
<< " Error: " << maybe_registered_services_str.getErr();
return;
}
stringstream ss(maybe_registered_services_str.unpack());
cereal::JSONInputArchive ar(ss);
ar(cereal::make_nvp("Registered Services", pending_services));
dbgInfo(D_ORCHESTRATOR)
<< "Orchestration pending services loaded from file."
<< " File: "
<< registered_services_file
<< ". Registered Services:";
for (const auto &id_service_pair : pending_services) {
const auto &service = id_service_pair.second;
dbgInfo(D_ORCHESTRATOR)
<< "Service name: "
<< service.getServiceName()
<< ", Service ID: "
<< service.getServiceID()
<< ", Service port: "
<< service.getPort();
}
}
void
ServiceController::Impl::writeRegisteredServicesToFile()
{
dbgFlow(D_ORCHESTRATOR);
auto registered_services_file = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/orchestrations_registered_services.json",
"orchestration",
"Orchestration registered services"
);
ofstream ss(registered_services_file);
cereal::JSONOutputArchive ar(ss);
ar(cereal::make_nvp("Registered Services", registered_services));
dbgInfo(D_ORCHESTRATOR)
<< "Orchestration registered services file has been updated. File: "
<< registered_services_file
<< ". Registered Services:";
for (const auto &id_service_pair : registered_services) {
const auto &service = id_service_pair.second;
dbgInfo(D_ORCHESTRATOR)
<< "Service name: "
<< service.getServiceName()
<< ", Service ID: "
<< service.getServiceID()
<< ", Service port: "
<< service.getPort();
}
}
void
ServiceController::Impl::cleanUpVirtualFiles()
{
const string file_list_cmd =
"ls " +
getConfigurationWithDefault<string>(
filesystem_prefix + "/conf",
"orchestration",
"Configuration directory"
) +
" | grep 'tenant_*' | cut -d '_' -f 2";
auto shell_cmd = Singleton:: Consume<I_ShellCmd>::by<ServiceController>();
auto tenant_manager = Singleton::Consume<I_TenantManager>::by<ServiceController>();
auto result = shell_cmd->getExecOutput(file_list_cmd);
if (!result.ok()) return;
set<string> tenants_on_agent;
istringstream parsig(*result);
while (!parsig.eof()) {
string tenant_id;
getline(parsig, tenant_id);
if (!tenant_id.empty()) tenants_on_agent.insert(tenant_id);
}
for (const auto &active_tenant: tenant_manager->fetchActiveTenants()) {
tenants_on_agent.erase(active_tenant);
}
for (const auto &none_active_tenant: tenants_on_agent) {
// remove files;
string settings_file = filesystem_prefix + "/conf/"+ none_active_tenant + "_settings.json";
string tenant_dir = filesystem_prefix + "/conf/tenant_"+ none_active_tenant;
Singleton::Consume<I_OrchestrationTools>::by<ServiceController>()->removeFile(settings_file);
rmdir(tenant_dir.c_str());
}
}
map<string, PortNumber>
ServiceController::Impl::getServiceToPortMap()
{
map<string, PortNumber> ports_map;
for (auto const& entry: registered_services) {
const string &service = entry.first;
PortNumber port = entry.second.getPort();
ports_map[service] = port;
}
for (auto const& entry: pending_services) {
const string &service = entry.first;
PortNumber port = entry.second.getPort();
ports_map[service] = port;
}
return ports_map;
}
void
ServiceController::Impl::registerServiceConfig(
const string &service_name,
PortNumber listening_port,
const vector<string> &relevant_configurations,
const string &service_id)
{
ServiceDetails service_config(
service_name,
listening_port,
relevant_configurations,
service_id
);
pending_services.erase(service_config.getServiceID());
pending_services.insert({service_config.getServiceID(), service_config});
}
bool
ServiceController::Impl::isServiceInstalled(const string &service_name)
{
return
registered_services.find(service_name) != registered_services.end() ||
pending_services.find(service_name) != pending_services.end();
}
void
ServiceController::Impl::refreshPendingServices()
{
dbgFlow(D_ORCHESTRATOR);
if (pending_services.empty()) return;
for (const auto &service : pending_services) {
registered_services.erase(service.first);
registered_services.insert({service.first, service.second});
dbgDebug(D_ORCHESTRATOR) << "Successfully registered service. Name: " << service.first;
}
pending_services.clear();
writeRegisteredServicesToFile();
}
bool
ServiceController::Impl::updateServiceConfiguration(
const string &new_policy_path,
const string &new_settings_path,
const vector<string> &new_data_files,
const string &tenant_id)
{
dbgFlow(D_ORCHESTRATOR)
<< "new_policy_path: "
<< new_policy_path
<< ", new_settings_path: "
<< new_settings_path
<< ", new_data_files: "
<< makeSeparatedStr(new_data_files, ",")
<< ". tenant_id: "
<< tenant_id;
if (!new_settings_path.empty()) {
settings_path = new_settings_path;
}
refreshPendingServices();
set<string> nano_services_to_update;
for (const auto &service : registered_services) {
if (new_settings_path != "") {
nano_services_to_update.insert(service.first);
continue;
}
for (const string &data : new_data_files) {
dbgTrace(D_ORCHESTRATOR) << "data: " << data;
if (service.second.isConfigurationRelevant(data)) {
dbgTrace(D_ORCHESTRATOR)
<< "data has relevant configuration, will update the service: "
<< service.first;
nano_services_to_update.insert(service.first);
break;
}
}
}
if (new_policy_path == "") {
dbgDebug(D_ORCHESTRATOR) << "Policy file was not updated. Sending reload command regarding settings and data";
return sendSignalForServices(nano_services_to_update, "");
}
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ServiceController>();
Maybe<string> loaded_json = orchestration_tools->readFile(new_policy_path);
if (!loaded_json.ok()) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to load new file: "
<< new_policy_path
<< ". Error: "
<< loaded_json.getErr();
return false;
}
auto all_security_policies = orchestration_tools->jsonObjectSplitter(loaded_json.unpack(), tenant_id);
if (!all_security_policies.ok()) {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to parse json file: "
<< new_policy_path
<< ". Error: "
<< all_security_policies.getErr();
return false;
}
bool was_policy_updated = true;
const string version_param = "version";
string version_value;
for (auto &single_policy : all_security_policies.unpack()) {
if (single_policy.first == version_param) {
version_value = single_policy.second;
version_value.erase(remove(version_value.begin(), version_value.end(), '\"'), version_value.end());
update_policy_version = version_value;
continue;
}
dbgDebug(D_ORCHESTRATOR) << "Starting to update policy file. Policy type: " << single_policy.first;
string dir = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf",
"orchestration",
"Configuration directory"
);
if (tenant_id != "") {
dir = dir + "/tenant_" + tenant_id;
if (!orchestration_tools->doesDirectoryExist(dir)) {
if (orchestration_tools->createDirectory(dir)) {
dbgTrace(D_ORCHESTRATOR) << "Created new configuration directory for tenant " << tenant_id;
} else {
dbgError(D_ORCHESTRATOR) << "Failed to create configuration directory for tenant "<< tenant_id;
return false;
}
}
}
string policy_file_path = getPolicyConfigPath(single_policy.first, Config::ConfigFileType::Policy, tenant_id);
auto update_config_result = updateServiceConfigurationFile(
single_policy.first,
policy_file_path,
single_policy.second
);
if (!update_config_result) {
dbgWarning(D_ORCHESTRATOR) << "Failed to update policy file. Policy name: " << single_policy.first;
was_policy_updated = false;
continue;
}
dbgInfo(D_ORCHESTRATOR) << "Successfully updated policy file. Policy name: " << single_policy.first;
auto orc_status = Singleton::Consume<I_OrchestrationStatus>::by<ServiceController>();
orc_status->setServiceConfiguration(
single_policy.first,
policy_file_path,
OrchestrationStatusConfigType::POLICY
);
if (tenant_id != "") {
auto instances = Singleton::Consume<I_TenantManager>::by<ServiceController>()->getInstances(tenant_id);
for (const auto &instance_id: instances) {
auto relevant_service = registered_services.find(instance_id);
if (relevant_service == registered_services.end()) {
dbgWarning(D_ORCHESTRATOR) << "Could not find registered service. Service Id: " << instance_id;
continue;
}
if (relevant_service->second.isConfigurationRelevant(single_policy.first)) {
nano_services_to_update.insert(instance_id);
}
}
} else {
for (const auto &service : registered_services) {
if (service.second.isConfigurationRelevant(single_policy.first)) {
nano_services_to_update.insert(service.first);
}
}
}
}
was_policy_updated &= sendSignalForServices(nano_services_to_update, version_value);
dbgTrace(D_ORCHESTRATOR) << "was_policy_updated: " << (was_policy_updated ? "true" : "false");
if (was_policy_updated) {
string config_file_path;
string base_path = filesystem_prefix + "/conf/" + (tenant_id != "" ? "tenant_" + tenant_id + "/" : "");
config_file_path = getConfigurationWithDefault<string>(
base_path + "policy.json",
"orchestration",
"Policy file path"
);
if (new_policy_path.compare(config_file_path) == 0) {
dbgDebug(D_ORCHESTRATOR) << "Enforcing the default policy file";
policy_version = version_value;
return true;
}
string backup_ext = getConfigurationWithDefault<string>(".bk", "orchestration", "Backup file extension");
// Save the new configuration file.
if (!orchestration_tools->copyFile(new_policy_path, config_file_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to save the policy file.";
return false;
}
// Backup the current configuration file.
uint max_backup_attempts = 3;
bool is_backup_succeed = false;
string backup_file = config_file_path + backup_ext;
I_MainLoop *mainloop = Singleton::Consume<I_MainLoop>::by<ServiceController>();
for (size_t i = 0; i < max_backup_attempts; i++) {
if (orchestration_tools->copyFile(new_policy_path, backup_file)) {
is_backup_succeed = true;
break;
}
mainloop->yield(false);
}
if (!is_backup_succeed) {
dbgWarning(D_ORCHESTRATOR) << "Failed to back up the policy file.";
return false;
}
policy_version = version_value;
}
return was_policy_updated;
}
bool
ServiceController::Impl::sendSignalForServices(
const set<string> &nano_services_to_update,
const string &policy_version)
{
dbgFlow(D_ORCHESTRATOR);
for (auto &service_id : nano_services_to_update) {
auto nano_service = registered_services.find(service_id);
if (nano_service == registered_services.end()) {
dbgWarning(D_ORCHESTRATOR) << "Could not find registered service. Service Id: " << service_id;
continue;
}
++configuration_id;
auto reconf_status = nano_service->second.sendNewConfigurations(configuration_id, policy_version);
if (reconf_status == ReconfStatus::INACTIVE) {
dbgWarning(D_ORCHESTRATOR) << "Erasing details regarding inactive service " << service_id;
registered_services.erase(service_id);
writeRegisteredServicesToFile();
}
if (reconf_status == ReconfStatus::FAILED) {
dbgDebug(D_ORCHESTRATOR) << "The reconfiguration failed for serivce " << service_id;
services_reconf_status.clear();
services_reconf_names.clear();
return false;
}
}
int reconf_timeout = getConfigurationWithDefault(600, "orchestration", "Reconfiguration timeout seconds");
auto timer = Singleton::Consume<I_TimeGet>::by<ServiceController>();
auto current_timeout = timer->getMonotonicTime() + chrono::seconds(reconf_timeout);
while(timer->getMonotonicTime() < current_timeout) {
switch (getUpdatedReconfStatus()) {
case ReconfStatus::SUCCEEDED: {
dbgDebug(D_ORCHESTRATOR) << "The reconfiguration was successfully completed for all the services";
services_reconf_status.clear();
services_reconf_names.clear();
return true;
}
case ReconfStatus::IN_PROGRESS: {
dbgTrace(D_ORCHESTRATOR) << "Reconfiguration in progress...";
Singleton::Consume<I_MainLoop>::by<ServiceController>()->yield(chrono::seconds(2));
break;
}
case ReconfStatus::FAILED: {
for(auto &status : services_reconf_status) {
if (status.second == ReconfStatus::FAILED) {
dbgDebug(D_ORCHESTRATOR)
<< "The reconfiguration failed for serivce "
<< services_reconf_names[status.first];
}
}
services_reconf_status.clear();
services_reconf_names.clear();
return false;
}
case ReconfStatus::INACTIVE: {
dbgError(D_ORCHESTRATOR) << "Reached inactive state in the middle of reconfiguration!";
services_reconf_status.clear();
services_reconf_names.clear();
return false;
}
}
}
dbgDebug(D_ORCHESTRATOR) << "The reconfiguration has reached a timeout";
services_reconf_status.clear();
services_reconf_names.clear();
return false;
}
bool
ServiceController::Impl::updateServiceConfigurationFile(
const string &configuration_name,
const string &configuration_file_path,
const string &new_configuration_path)
{
dbgFlow(D_ORCHESTRATOR) << "Updating configuration. Config Name: " << configuration_name;
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<ServiceController>();
if (orchestration_tools->doesFileExist(configuration_file_path)) {
Maybe<string> old_configuration = orchestration_tools->readFile(configuration_file_path);
if (old_configuration.ok()) {
bool service_changed = old_configuration.unpack().compare(new_configuration_path) != 0;
if (service_changed == false) {
dbgDebug(D_ORCHESTRATOR) << "There is no update for policy file: " << configuration_file_path;
return true;
}
dbgDebug(D_ORCHESTRATOR)
<< "Starting to update " << configuration_file_path << " to " << new_configuration_path;
string old_configuration_backup_path = configuration_file_path + getConfigurationWithDefault<string>(
".bk",
"orchestration",
"Backup file extension"
);
if (orchestration_tools->copyFile(configuration_file_path, old_configuration_backup_path)) {
dbgDebug(D_ORCHESTRATOR) << "Backup of policy file has been created in: " << configuration_file_path;
} else {
dbgWarning(D_ORCHESTRATOR) << "Failed to backup policy file";
return false;
}
} else {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to read current policy file "
<< configuration_file_path
<< ". Error: "
<< old_configuration.getErr();
return false;
}
}
if (orchestration_tools->writeFile(new_configuration_path, configuration_file_path)) {
dbgDebug(D_ORCHESTRATOR) << "New policy file has been saved in: " << configuration_file_path;
} else {
dbgWarning(D_ORCHESTRATOR) << "Failed to save new policy file";
return false;
}
dbgInfo(D_ORCHESTRATOR) << "Successfully updated policy file: " << configuration_file_path;
return true;
}
ServiceController::ServiceController() : Component("ServiceController"), pimpl(make_unique<Impl>()) {}
ServiceController::~ServiceController() {}
void
ServiceController::init()
{
pimpl->init();
}
const string &
ServiceController::Impl::getPolicyVersion() const
{
return policy_version;
}
const string &
ServiceController::Impl::getUpdatePolicyVersion() const
{
return update_policy_version;
}
void
ServiceController::Impl::updateReconfStatus(int id, ReconfStatus status)
{
if (services_reconf_status.find(id) == services_reconf_status.end()) {
dbgError(D_ORCHESTRATOR) << "Service reconfiguration monitor received illegal id :" << id;
return;
}
services_reconf_status[id] = status;
}
void
ServiceController::Impl::startReconfStatus(
int id,
ReconfStatus status,
const string &service_name,
const string &service_id)
{
services_reconf_status.emplace(id, status);
services_reconf_names.emplace(id, service_name);
services_reconf_ids.emplace(id, service_id);
}
SASAL_END

View File

@@ -0,0 +1,7 @@
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
service_controller_ut
"service_controller_ut.cc"
"service_controller;rest;config;environment;metric;event_is;shell_cmd;orchestration_modules;logging;agent_details;-lboost_regex"
)

View File

@@ -0,0 +1,3 @@
add_library(update_communication update_communication.cc hybrid_communication.cc fog_communication.cc fog_authenticator.cc local_communication.cc)
add_subdirectory(update_communication_ut)

View File

@@ -0,0 +1,572 @@
// 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 "fog_communication.h"
#include "rest.h"
#include "config.h"
#include "log_generator.h"
#include "agent_details.h"
#include "version.h"
#include "sasal.h"
#include <algorithm>
#include <map>
#include <vector>
SASAL_START // Orchestration - Communication
using namespace std;
using namespace cereal;
using HTTPMethod = I_Messaging::Method;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
function<Maybe<FogAuthenticator::AccessToken>()> FogAuthenticator::AccessTokenProvider::getAccessToken = nullptr;
FogAuthenticator::AccessToken::AccessToken(const string &_token, chrono::seconds _expiration)
:
token(_token),
expiration(_expiration)
{
received_time = Singleton::Consume<I_TimeGet>::by<FogAuthenticator>()->getMonotonicTime();
}
chrono::seconds
FogAuthenticator::AccessToken::getRemainingTime() const
{
return
expiration -
chrono::duration_cast<chrono::seconds>(
Singleton::Consume<I_TimeGet>::by<FogAuthenticator>()->getMonotonicTime() - received_time
);
}
void
FogAuthenticator::AccessTokenProvider::doCall()
{
if (getAccessToken != nullptr) {
auto access_token = getAccessToken();
if (access_token.ok()) {
auto encryptor = Singleton::Consume<I_Encryptor>::by<FogAuthenticator>();
token = encryptor->obfuscateXorBase64(access_token.unpack().getToken());
expiration = access_token.unpack().getRemainingTime().count();
}
}
}
FogAuthenticator::RegistrationData::RegistrationData(const string &token)
:
type(AuthenticationType::Token),
data(token)
{
}
FogAuthenticator::UserCredentials::UserCredentials(const string &_client_id, const string &_shared_secret)
:
client_id(_client_id),
shared_secret(_shared_secret)
{
}
void
FogAuthenticator::UserCredentials::serialize(JSONOutputArchive &out_ar) const
{
out_ar(
make_nvp("client_id", client_id),
make_nvp("shared_secret", shared_secret)
);
}
void
FogAuthenticator::UserCredentials::serialize(JSONInputArchive &in_ar)
{
in_ar(
make_nvp("client_id", client_id),
make_nvp("shared_secret", shared_secret)
);
if (client_id.empty() || shared_secret.empty()) {
throw cereal::Exception("Agent credentials can't be empty.");
}
}
void
FogAuthenticator::RegistrationData::serialize(JSONInputArchive &in_ar)
{
string type_as_string;
static const map<string, AuthenticationType> StringToAuthenticationType {
{ "token", AuthenticationType::Token },
{ "presharedsecret", AuthenticationType::PresharedSecret }
};
in_ar(
make_nvp("registration type", type_as_string),
make_nvp("registration data", data)
);
if (type_as_string.empty()) throw cereal::Exception("registration type can't be empty.");
if (data.empty()) throw cereal::Exception("registration data can't be empty.");
auto auth_type = StringToAuthenticationType.find(type_as_string);
if (auth_type == StringToAuthenticationType.end()) throw cereal::Exception("Unsupported registration type.");
type = auth_type->second;
}
void
FogAuthenticator::RegistrationData::serialize(JSONOutputArchive &out_ar) const
{
static const EnumArray<AuthenticationType, string> AuthenticationTypeString {
"token",
"presharedsecret"
};
out_ar(
make_nvp("authenticationMethod", AuthenticationTypeString[type]),
make_nvp("data", data)
);
}
Maybe<FogAuthenticator::UserCredentials>
FogAuthenticator::registerAgent(
const FogAuthenticator::RegistrationData &reg_data,
const string &name,
const string &type,
const string &platform,
const string &architecture) const
{
dbgInfo(D_ORCHESTRATOR) << "Starting agent registration to fog";
auto details_resolver = Singleton::Consume<I_DetailsResolver>::by<FogAuthenticator>();
RegistrationRequest request(
reg_data,
name,
type,
platform,
architecture,
details_resolver->getAgentVersion()
);
request << make_pair("agent_version", details_resolver->getAgentVersion());
if (required_security_apps.size() > 0) {
request << make_pair("require", makeSeparatedStr(required_security_apps, ";"));
}
auto nginx_data = details_resolver->parseNginxMetadata();
if (nginx_data.ok()) {
string nginx_version;
string config_opt;
string cc_opt;
tie(config_opt, cc_opt, nginx_version) = nginx_data.unpack();
request << make_pair("nginxVersion", nginx_version);
request << make_pair("configureOpt", config_opt);
request << make_pair("extraCompilerOpt", cc_opt);
} else {
dbgDebug(D_ORCHESTRATOR) << nginx_data.getErr();
}
for (const pair<string, string> details : details_resolver->getResolvedDetails()) {
request << details;
}
if (details_resolver->isReverseProxy()) {
request << make_pair("reverse_proxy", "true");
}
if (details_resolver->isKernelVersion3OrHigher()) {
request << make_pair("isKernelVersion3OrHigher", "true");
}
if (details_resolver->isGwNotVsx()) {
request << make_pair("isGwNotVsx", "true");
}
if (details_resolver->isVersionEqualOrAboveR8110()) {
request << make_pair("isVersionEqualOrAboveR8110", "true");
}
#if defined(gaia) || defined(smb)
if (details_resolver->compareCheckpointVersion(8100, std::greater_equal<int>())) {
request << make_pair("isCheckpointVersionGER81", "true");
}
#endif // gaia || smb
auto fog_messaging = Singleton::Consume<I_Messaging>::by<FogAuthenticator>();
if (fog_messaging->sendObject(request, HTTPMethod::POST, fog_address_ex + "/agents")) {
dbgDebug(D_ORCHESTRATOR) << "Agent has registered successfully.";
auto i_agent_details = Singleton::Consume<I_AgentDetails>::by<FogAuthenticator>();
i_agent_details->setAgentId(request.getAgentId());
i_agent_details->setProfileId(request.getProfileId());
i_agent_details->setTenantId(request.getTenantId());
i_agent_details->writeAgentDetails();
auto orc_status = Singleton::Consume<I_OrchestrationStatus>::by<FogAuthenticator>();
orc_status->setAgentDetails(request.getAgentId(), request.getProfileId(), request.getTenantId());
return UserCredentials(request.getClientId(), request.getSharedSecret());
}
LogGen log(
"We suggest to check that your Agent Profile is defined and enforced",
ReportIS::Audience::SECURITY,
ReportIS::Severity::INFO,
ReportIS::Priority::MEDIUM,
LogField("source", "fog_communication"),
ReportIS::Tags::ORCHESTRATOR
);
return genError("Failed to register agent with the Fog");
}
Maybe<FogAuthenticator::AccessToken>
FogAuthenticator::getAccessToken(const UserCredentials &user_credentials) const
{
dbgDebug(D_ORCHESTRATOR) << "Requesting token from fog.";
static const string grant_type_string = "/oauth/token?grant_type=client_credentials";
TokenRequest request = TokenRequest();
auto fog_messaging = Singleton::Consume<I_Messaging>::by<FogAuthenticator>();
auto sending_result = fog_messaging->sendObject(
request,
HTTPMethod::POST,
fog_address_ex + grant_type_string,
buildBasicAuthHeader(user_credentials.getClientId(), user_credentials.getSharedSecret())
);
if (sending_result) {
auto data_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/data/",
"encryptor",
"Data files directory"
);
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
if (!orchestration_tools->writeFile(request.getAccessToken(), data_path + session_token_file_name)) {
return genError("Failed to write new access token to file");
}
dbgInfo(D_ORCHESTRATOR) << "New access token was saved";
fog_messaging->loadAccessToken();
return AccessToken(request.getAccessToken(), chrono::seconds(request.getExpirationTime()));
}
return genError("Failed to get access token.");
}
Maybe<FogAuthenticator::RegistrationData>
FogAuthenticator::getRegistrationData()
{
if (!otp.empty()) {
reg_data = RegistrationData(otp);
return reg_data;
}
const char *env_otp = getenv("NANO_AGENT_TOKEN");
if (env_otp) {
dbgInfo(D_ORCHESTRATOR) << "Loading registration token from environment";
return RegistrationData(env_otp);
}
if (reg_data.ok()) {
dbgInfo(D_ORCHESTRATOR) << "Loading registration token from cache";
return reg_data;
}
auto reg_data_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/registration-data.json",
"orchestration",
"Registration data Path"
);
dbgDebug(D_ORCHESTRATOR) << "Loading registration data from " << reg_data_path;
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
auto raw_reg_data = orchestration_tools->readFile(reg_data_path);
if (!raw_reg_data.ok()) return genError(raw_reg_data.getErr());
dbgTrace(D_ORCHESTRATOR) << "Successfully loaded the registration data";
auto decoded_reg_data = orchestration_tools->base64Decode(raw_reg_data.unpack());
reg_data = orchestration_tools->jsonStringToObject<RegistrationData>(decoded_reg_data);
if (reg_data.ok()) {
dbgTrace(D_ORCHESTRATOR) << "Registration token has been converted to an object";
}
return reg_data;
}
bool
FogAuthenticator::saveCredentialsToFile(const UserCredentials &user_credentials) const
{
auto data_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/data/",
"encryptor",
"Data files directory"
);
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
auto cred_str = orchestration_tools->objectToJson<UserCredentials>(user_credentials);
if (!cred_str.ok()) {
dbgWarning(D_ORCHESTRATOR) << "Failed to parse user credentials to JSON. Error: " << cred_str.getErr();
return false;
}
return orchestration_tools->writeFile(cred_str.unpack(), data_path + user_cred_file_name);
}
void
FogAuthenticator::initRestAPI()
{
AccessTokenProvider::getAccessToken = [this] () {
return access_token;
};
auto rest = Singleton::Consume<I_RestApi>::by<FogAuthenticator>();
rest->addRestCall<FogAuthenticator::AccessTokenProvider>(RestAction::SHOW, "access-token");
}
Maybe<FogAuthenticator::UserCredentials>
FogAuthenticator::getCredentialsFromFile() const
{
auto data_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/data/",
"encryptor",
"Data files directory"
);
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
auto encrypted_cred = orchestration_tools->readFile(data_path + user_cred_file_name);
if (!encrypted_cred.ok()) return genError(encrypted_cred.getErr());
dbgTrace(D_ORCHESTRATOR) << "Read the user credentials from the file";
return orchestration_tools->jsonStringToObject<UserCredentials>(encrypted_cred.unpack());
}
Maybe<FogAuthenticator::UserCredentials>
FogAuthenticator::getCredentials()
{
auto maybe_credentials = getCredentialsFromFile();
if (maybe_credentials.ok()) {
return maybe_credentials;
}
auto reg_data = getRegistrationData();
if (!reg_data.ok()) {
return genError("Failed to load a valid registration token, Error: " + reg_data.getErr());
}
auto details_resolver = Singleton::Consume<I_DetailsResolver>::by<FogAuthenticator>();
Maybe<string> name = details_resolver->getHostname();
if (!name.ok()) return name.passErr();
Maybe<string> platform = details_resolver->getPlatform();
if (!platform.ok()) return platform.passErr();
Maybe<string> arch = details_resolver->getArch();
if (!arch.ok()) return arch.passErr();
string type = getConfigurationWithDefault<string>("Embedded", "orchestration", "Agent type");
maybe_credentials = registerAgent(reg_data.unpack(), *name, type, *platform, *arch);
auto orc_status = Singleton::Consume<I_OrchestrationStatus>::by<FogAuthenticator>();
orc_status->setRegistrationDetails(*name, type, *platform, *arch);
if (!maybe_credentials.ok()) return maybe_credentials;
auto credentials = maybe_credentials.unpack();
auto token_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/registration-data.json",
"orchestration",
"Registration data Path"
);
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
if (saveCredentialsToFile(credentials)) {
if (!orchestration_tools->removeFile(token_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to remove one time token file";
}
return credentials;
}
dbgWarning(D_ORCHESTRATOR) << "Failed to save credentials to file";
Singleton::Consume<I_MainLoop>::by<FogAuthenticator>()->addOneTimeRoutine(
I_MainLoop::RoutineType::Offline,
[this, credentials, token_path] ()
{
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
static uint retry_counter = 1;
while (!saveCredentialsToFile(credentials)) {
dbgTrace(D_ORCHESTRATOR) << "Failed to save credentials to file, retry number: " << retry_counter++;
Singleton::Consume<I_MainLoop>::by<FogAuthenticator>()->yield(chrono::seconds(60));
}
if (!orchestration_tools->removeFile(token_path)) {
dbgWarning(D_ORCHESTRATOR) << "Failed to remove one time token file";
}
},
"Fog credential save to file"
);
return credentials;
}
string
FogAuthenticator::buildBasicAuthHeader(const string &username, const string &pass) const
{
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
auto auth_encode = orchestration_tools->base64Encode(username + ":" + pass);
return "Authorization: Basic " + auth_encode + "\r\n";
}
string
FogAuthenticator::buildOAuth2Header(const string &token) const
{
return "Authorization: Bearer " + token + "\r\n";
}
void
FogAuthenticator::setAddressExtenesion(const std::string &extension)
{
fog_address_ex = extension;
}
Maybe<void>
FogAuthenticator::authenticateAgent()
{
const int min_expiration_time = 10;
if (!credentials.ok()) {
dbgDebug(D_ORCHESTRATOR) << "Getting Agent credentials.";
auto orc_status = Singleton::Consume<I_OrchestrationStatus>::by<FogAuthenticator>();
credentials = getCredentials();
if (!credentials.ok()) {
orc_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::FAILED,
credentials.getErr()
);
return genError(credentials.getErr());
}
orc_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::SUCCESS
);
}
auto mainloop = Singleton::Consume<I_MainLoop>::by<FogAuthenticator>();
if (!mainloop->doesRoutineExist(routine)) {
routine = mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
[this, min_expiration_time] ()
{
uint expiration_time;
uint pre_expire_time = 0;
do {
expiration_time = 20;
auto orc_status = Singleton::Consume<I_OrchestrationStatus>::by<FogAuthenticator>();
access_token = getAccessToken(credentials.unpack());
if (access_token.ok()) {
pre_expire_time = getConfigurationWithDefault<int>(
120,
"fog communication",
"Time (seconds) to renew token prior its expiration"
);
expiration_time = access_token.unpack().getExpiration();
dbgInfo(D_ORCHESTRATOR) << "New token was received, expiration time: " << expiration_time;
orc_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::SUCCESS
);
} else {
dbgWarning(D_ORCHESTRATOR)
<< "Failed to receive access token. Error: " << access_token.getErr();
orc_status->setFieldStatus(
OrchestrationStatusFieldType::REGISTRATION,
OrchestrationStatusResult::FAILED,
access_token.getErr()
);
}
int next_session_req = max(
static_cast<int>(expiration_time - pre_expire_time),
min_expiration_time
);
dbgDebug(D_ORCHESTRATOR)
<< "Schedule the next re-activate session token. Seconds: "
<< next_session_req;
Singleton::Consume<I_MainLoop>::by<FogAuthenticator>()->yield(chrono::seconds(next_session_req));
} while (1);
},
"Fog communication token periodic update",
true
);
// Wait for the access token mainloop
mainloop->yield(chrono::seconds(min_expiration_time + 1));
}
if (!access_token.ok()) return genError(access_token.getErr());
return Maybe<void>();
}
void
FogAuthenticator::preload()
{
registerExpectedConfiguration<string>("orchestration", "Agent type");
registerExpectedConfiguration<string>("orchestration", "OTP Token Path");
registerExpectedConfiguration<string>("orchestration", "User Credentials Path");
registerExpectedConfiguration<int>("fog communication", "Time (seconds) to renew token prior its expiration");
}
void
FogAuthenticator::loadRequiredSecurityApps()
{
auto required_apps_file_path = getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/support-practices.txt",
"orchestration",
"Supported practices file path"
);
auto orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
if (orchestration_tools->doesFileExist(required_apps_file_path)) {
try {
ifstream input_stream(required_apps_file_path);
if (!input_stream) {
dbgDebug(D_ORCHESTRATOR)
<< "Cannot open the file with required security apps"
<< "File: " << required_apps_file_path;
return;
}
string required_security_app;
while (getline(input_stream, required_security_app)) {
required_security_apps.push_back(required_security_app);
}
input_stream.close();
} catch (const ifstream::failure &exception) {
dbgWarning(D_ORCHESTRATOR)
<< "Cannot read the file with required security app lists."
<< " File: " << required_apps_file_path
<< " Error: " << exception.what();
}
}
}
void
FogAuthenticator::init()
{
filesystem_prefix = getFilesystemPathConfig();
dbgTrace(D_ORCHESTRATOR) << "Initializing Fog communication, file system path prefix: " << filesystem_prefix;
loadRequiredSecurityApps();
initRestAPI();
}
SASAL_END

View File

@@ -0,0 +1,89 @@
// 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 "fog_communication.h"
#include "rest.h"
#include "config.h"
#include "log_generator.h"
#include "agent_details.h"
#include "version.h"
#include "sasal.h"
#include <algorithm>
#include <map>
#include <vector>
SASAL_START // Orchestration - Communication
using namespace std;
using namespace cereal;
using HTTPMethod = I_Messaging::Method;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
Maybe<void>
FogCommunication::getUpdate(CheckUpdateRequest &request)
{
if (!access_token.ok()) return genError("Acccess Token not available.");
auto unpacked_access_token = access_token.unpack().getToken();
static const string check_update_str = "/api/v2/agents/resources";
auto request_status = Singleton::Consume<I_Messaging>::by<FogCommunication>()->sendObject(
request,
HTTPMethod::POST,
fog_address_ex + check_update_str,
buildOAuth2Header(unpacked_access_token)
);
if (!request_status) {
dbgDebug(D_ORCHESTRATOR) << "Failed to get response after check update request.";
return genError("Failed to request updates");
}
dbgDebug(D_ORCHESTRATOR) << "Got response after check update request.";
return Maybe<void>();
}
Maybe<string>
FogCommunication::downloadAttributeFile(const GetResourceFile &resourse_file)
{
if (!access_token.ok()) return genError("Acccess Token not available.");
auto unpacked_access_token = access_token.unpack().getToken();
static const string file_attribute_str = "/api/v2/agents/resources/";
Maybe<string> attribute_file = Singleton::Consume<I_Messaging>::by<FogCommunication>()->downloadFile(
resourse_file,
resourse_file.getRequestMethod(),
fog_address_ex + file_attribute_str + resourse_file.getFileName(),
buildOAuth2Header(unpacked_access_token) // Header
);
return attribute_file;
}
Maybe<void>
FogCommunication::sendPolicyVersion(const string &policy_version) const
{
PolicyVersionPatchRequest request(policy_version);
auto fog_messaging = Singleton::Consume<I_Messaging>::by<FogCommunication>();
if (fog_messaging->sendNoReplyObject(request, HTTPMethod::PATCH, fog_address_ex + "/agents")) {
dbgInfo(D_ORCHESTRATOR)
<< "Patch request was sent successfully to the fog."
<< " Policy version: "
<< policy_version;
return Maybe<void>();
}
return genError("Failed to patch policy version");
}
SASAL_END

View File

@@ -0,0 +1,128 @@
// 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 "hybrid_communication.h"
#include "rest.h"
#include "config.h"
#include "log_generator.h"
#include "agent_details.h"
#include "version.h"
#include "sasal.h"
#include <algorithm>
#include <map>
#include <vector>
SASAL_START // Orchestration - Communication
using namespace std;
using HTTPMethod = I_Messaging::Method;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
void
HybridCommunication::init()
{
FogAuthenticator::init();
dbgTrace(D_ORCHESTRATOR) << "Initializing the Hybrid Communication Component";
if (getConfigurationFlag("otp") != "") {
otp = getConfigurationFlag("otp");
} else {
otp = "cp-3fb5c718-5e39-47e6-8d5e-99b4bc5660b74b4b7fc8-5312-451d-a763-aaf7872703c0";
}
}
string
HybridCommunication::getChecksum(const string &policy_version)
{
dbgFlow(D_ORCHESTRATOR) << "Checking the policy Checksum";
string clean_plicy_version = policy_version;
if (!clean_plicy_version.empty() && clean_plicy_version[clean_plicy_version.size() - 1] == '\n')
clean_plicy_version.erase(clean_plicy_version.size() - 1);
curr_policy = Singleton::Consume<I_K8S_Policy_Gen>::by<HybridCommunication>()->parsePolicy(clean_plicy_version);
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
Maybe<string> file_checksum = orchestration_tools->calculateChecksum(
I_OrchestrationTools::SELECTED_CHECKSUM_TYPE,
Singleton::Consume<I_K8S_Policy_Gen>::by<HybridCommunication>()->getPolicyPath()
);
if (!file_checksum.ok()) {
dbgWarning(D_ORCHESTRATOR) << "Failed the policy checksum calculation";
return "";
}
return file_checksum.unpack();
}
Maybe<string>
HybridCommunication::getNewVersion()
{
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<FogAuthenticator>();
return orchestration_tools->readFile("/etc/cp/conf/k8s-policy-check.trigger");
}
Maybe<void>
HybridCommunication::getUpdate(CheckUpdateRequest &request)
{
dbgFlow(D_ORCHESTRATOR) << "Getting policy update in an Hybrid Communication";
auto maybe_new_version = getNewVersion();
if (!maybe_new_version.ok() || maybe_new_version == curr_version) {
request = CheckUpdateRequest("", "", "", "", "", "");
dbgDebug(D_ORCHESTRATOR) << "No new version is currently available";
return Maybe<void>();
}
auto policy_checksum = request.getPolicy();
auto offline_policy_checksum = getChecksum(maybe_new_version.unpack());
string policy_response = "";
if (!policy_checksum.ok() || offline_policy_checksum != policy_checksum.unpack()) {
policy_response = offline_policy_checksum;
}
dbgDebug(D_ORCHESTRATOR)
<< "Local update response: "
<< " policy: "
<< (policy_response.empty() ? "has no change," : "has new update," );
request = CheckUpdateRequest("", policy_response, "", "", "", "");
curr_version = *maybe_new_version;
return Maybe<void>();
}
Maybe<string>
HybridCommunication::downloadAttributeFile(const GetResourceFile &resourse_file)
{
auto file_name = resourse_file.getFileName();
if (file_name.compare("policy") == 0) {
return curr_policy;
}
dbgWarning(D_ORCHESTRATOR) << "Failed downloading the attribute files";
return string("");
}
Maybe<void>
HybridCommunication::sendPolicyVersion(const string &policy_version) const
{
dbgFlow(D_ORCHESTRATOR);
policy_version.empty();
return Maybe<void>();
}
SASAL_END

View File

@@ -0,0 +1,187 @@
// 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 "local_communication.h"
#include "config.h"
#include "sasal.h"
SASAL_START // Orchestration - Communication
using namespace std;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
void
LocalCommunication::init()
{
filesystem_prefix = getFilesystemPathConfig();
dbgTrace(D_ORCHESTRATOR) << "Initializing Local communication, file system path prefix: " << filesystem_prefix;
}
void
LocalCommunication::preload()
{
registerExpectedConfiguration<string>("orchestration", "Offline manifest file path");
registerExpectedConfiguration<string>("orchestration", "Offline settings file path");
registerExpectedConfiguration<string>("orchestration", "Offline policy file path");
registerExpectedConfiguration<string>("orchestration", "Offline Data file path");
}
Maybe<void>
LocalCommunication::authenticateAgent()
{
return Maybe<void>();
}
string
LocalCommunication::getChecksum(const string &file_path)
{
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<LocalCommunication>();
Maybe<string> file_checksum = orchestration_tools->calculateChecksum(
I_OrchestrationTools::SELECTED_CHECKSUM_TYPE,
file_path
);
if (!file_checksum.ok()) return "";
return file_checksum.unpack();
}
Maybe<void>
LocalCommunication::getUpdate(CheckUpdateRequest &request)
{
auto manifest_checksum = request.getManifest();
auto policy_checksum = request.getPolicy();
auto settings_checksum =request.getSettings();
auto data_checksum = request.getData();
auto offline_manifest_checksum = getChecksum(
getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_manifest.json",
"orchestration",
"Offline Manifest file path"
)
);
auto offline_policy_checksum = getChecksum(
getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_policy.json",
"orchestration",
"Offline Policy file path"
)
);
auto offline_settings_checksum = getChecksum(
getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_settings.json",
"orchestration",
"Offline Settings file path"
)
);
auto offline_data_checksum = getChecksum(
getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/data/offline_data.json",
"orchestration",
"Offline Data file path"
)
);
string manifest_response = "";
string policy_response = "";
string settings_response = "";
string data_response = "";
if (!manifest_checksum.ok() || offline_manifest_checksum != manifest_checksum.unpack()) {
manifest_response = offline_manifest_checksum;
}
if (!policy_checksum.ok() || offline_policy_checksum != policy_checksum.unpack()) {
policy_response = offline_policy_checksum;
}
if (!settings_checksum.ok() || offline_settings_checksum != settings_checksum.unpack()) {
settings_response = offline_settings_checksum;
}
if (!data_checksum.ok() || offline_data_checksum != data_checksum.unpack()) {
data_response = offline_data_checksum;
}
dbgDebug(D_ORCHESTRATOR) << "Local update response, "
<< " manifest: " << (manifest_response.empty() ? "has no change," : "has new update,")
<< " policy: " << (policy_response.empty() ? "has no change," : "has new update," )
<< " settings: " << (settings_response.empty() ? "has no change" : "has new update")
<< " data: " << (data_response.empty() ? "has no change" : "has new update");
request = CheckUpdateRequest(manifest_response, policy_response, settings_response, data_response, "", "");
return Maybe<void>();
}
Maybe<string>
LocalCommunication::downloadAttributeFile(const GetResourceFile &resourse_file)
{
auto file_name = resourse_file.getFileName();
I_OrchestrationTools *orchestration_tools = Singleton::Consume<I_OrchestrationTools>::by<LocalCommunication>();
if (file_name.compare("policy") == 0) {
return orchestration_tools->readFile(getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_policy.json",
"orchestration",
"Offline Policy file path"
));
}
if (file_name.compare("manifest") == 0) {
return orchestration_tools->readFile(getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_manifest.json",
"orchestration",
"Offline Manifest file path"
));
}
if (file_name.compare("settings") == 0) {
return orchestration_tools->readFile(getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_settings.json",
"orchestration",
"Offline Settings file path"
));
}
if (file_name.compare("virtualSettings") == 0) {
return orchestration_tools->readFile(getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_virtual_manifest.json",
"orchestration",
"Offline virtual Manifest file path"
));
}
if (file_name.compare("virtualPolicy") == 0) {
return orchestration_tools->readFile(getConfigurationWithDefault<string>(
filesystem_prefix + "/conf/offline_virtual_settings.json",
"orchestration",
"Offline virtual Settings file path"
));
}
dbgError(D_ORCHESTRATOR) << "Unknown resourse file name " << file_name;
return genError("Failed to detect resourse file name " + file_name);
}
void
LocalCommunication::setAddressExtenesion(const string &)
{
dbgTrace(D_ORCHESTRATOR) << "Agent in offline mode, no need for address setting";
return;
}
Maybe<void>
LocalCommunication::sendPolicyVersion(const string &) const
{
dbgTrace(D_ORCHESTRATOR) << "Agent in offline mode, no need to send policy version";
return Maybe<void>();
}
SASAL_END

View File

@@ -0,0 +1,148 @@
// 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 "update_communication.h"
#include <algorithm>
#include <map>
#include <vector>
#include "rest.h"
#include "config.h"
#include "log_generator.h"
#include "agent_details.h"
#include "version.h"
#include "sasal.h"
#include "i_encryptor.h"
#include "fog_authenticator.h"
#include "fog_communication.h"
#include "local_communication.h"
#include "hybrid_communication.h"
SASAL_START // Orchestration - Communication
using namespace std;
USE_DEBUG_FLAG(D_ORCHESTRATOR);
class UpdateCommunication::Impl
:
public ServerRest,
Singleton::Provide<I_UpdateCommunication>::From<UpdateCommunication>
{
public:
void
doCall() override
{
Singleton::Consume<I_MainLoop>::by<UpdateCommunication>()->stopAll();
status = "Operation mode had changed successfully";
}
void
preload()
{
FogAuthenticator::preload();
LocalCommunication::preload();
}
void
init()
{
auto rest = Singleton::Consume<I_RestApi>::by<UpdateCommunication>();
rest->addRestCall<UpdateCommunication::Impl>(RestAction::SET, "orchestration-mode");
setMode();
}
Maybe<void>
authenticateAgent()
{
return i_update_comm_impl->authenticateAgent();
}
Maybe<void>
getUpdate(CheckUpdateRequest &request) override
{
return i_update_comm_impl->getUpdate(request);
}
Maybe<void>
sendPolicyVersion(const string &policy_version) const override
{
return i_update_comm_impl->sendPolicyVersion(policy_version);
}
Maybe<string>
downloadAttributeFile(const GetResourceFile &resourse_file) override
{
return i_update_comm_impl->downloadAttributeFile(resourse_file);
}
void
setAddressExtenesion(const string &extension) override
{
i_update_comm_impl->setAddressExtenesion(extension);
}
void
fini()
{
i_update_comm_impl = nullptr;
}
private:
void
setMode()
{
if (getConfigurationFlag("orchestration-mode") == "offline_mode") {
i_update_comm_impl = make_unique<LocalCommunication>();
LocalCommunication *local_comm = static_cast<LocalCommunication*>(i_update_comm_impl.get());
local_comm->init();
return;
} else if (getConfigurationFlag("orchestration-mode") == "hybrid_mode") {
i_update_comm_impl = make_unique<HybridCommunication>();
HybridCommunication *local_comm = static_cast<HybridCommunication*>(i_update_comm_impl.get());
local_comm->init();
return;
}
i_update_comm_impl = make_unique<FogCommunication>();
FogCommunication *fog_comm = static_cast<FogCommunication*>(i_update_comm_impl.get());
fog_comm->init();
}
std::unique_ptr<I_UpdateCommunication> i_update_comm_impl = nullptr;
S2C_LABEL_PARAM(string, status, "status");
};
UpdateCommunication::UpdateCommunication() : Component("UpdateCommunication"), pimpl(make_unique<Impl>()) {}
UpdateCommunication::~UpdateCommunication() {}
void
UpdateCommunication::preload()
{
pimpl->preload();
}
void
UpdateCommunication::init()
{
pimpl->init();
}
void
UpdateCommunication::fini()
{
pimpl->fini();
}
SASAL_END

View File

@@ -0,0 +1,7 @@
link_directories(${BOOST_ROOT}/lib)
add_unit_test(
update_communication_ut
"local_communication_ut.cc"
"rest;version;orchestration_modules;update_communication;singleton;config;metric;event_is;logging;agent_details;-lboost_regex;"
)

View File

@@ -0,0 +1,233 @@
#include <string>
#include "local_communication.h"
#include "cptest.h"
#include "mock/mock_orchestration_tools.h"
#include "config.h"
#include "config_component.h"
#include "orchestration_status.h"
#include "mock/mock_mainloop.h"
#include "mock/mock_time_get.h"
using namespace std;
using namespace testing;
ostream &
operator<<(ostream &os, const tuple<OrchManifest, OrchPolicy, OrchSettings> &)
{
return os;
}
class LocalCommunicationTest: public Test
{
public:
LocalCommunicationTest()
{
local_communication.init();
}
void
preload()
{
local_communication.preload();
}
Maybe<void>
authenticateAgent()
{
return local_communication.authenticateAgent();
}
Maybe<void>
sendPolicyVersion(const string &version)
{
return local_communication.sendPolicyVersion(version);
}
Maybe<string>
downloadAttributeFile(const GetResourceFile &resourse_file)
{
return local_communication.downloadAttributeFile(resourse_file);
}
void
setAddressExtenesion(const string &ext)
{
local_communication.setAddressExtenesion(ext);
}
Maybe<void>
checkUpdate(CheckUpdateRequest &request)
{
return local_communication.getUpdate(request);
}
NiceMock<MockMainLoop> mock_mainloop;
NiceMock<MockTimeGet> mock_timer;
::Environment env;
ConfigComponent config_comp;
StrictMock<MockOrchestrationTools> mock_orc_tools;
OrchestrationStatus orc_status;
private:
LocalCommunication local_communication;
};
TEST_F(LocalCommunicationTest, doNothing)
{
}
TEST_F(LocalCommunicationTest, registerConfig)
{
env.preload();
env.init();
preload();
string config_json =
"{\n"
" \"orchestration\": {\n"
" \"Offline manifest file path\": [\n"
" {\n"
" \"context\": \"All()\",\n"
" \"value\": \"ABC\"\n"
" }\n"
" ],\n"
" \"Offline policy file path\": [\n"
" {\n"
" \"context\": \"All()\",\n"
" \"value\": \"qwe\"\n"
" }\n"
" ],\n"
" \"Offline settings file path\": [\n"
" {\n"
" \"context\": \"All()\",\n"
" \"value\": \"CCCC\"\n"
" }\n"
" ]\n"
" }\n"
"}";
istringstream ss(config_json);
Singleton::Consume<Config::I_Config>::from(config_comp)->loadConfiguration(ss);
EXPECT_THAT(getConfiguration<string>("orchestration", "Offline manifest file path"), IsValue("ABC"));
EXPECT_THAT(getConfiguration<string>("orchestration", "Offline policy file path"), IsValue("qwe"));
EXPECT_THAT(getConfiguration<string>("orchestration", "Offline settings file path"), IsValue("CCCC"));
env.fini();
}
TEST_F(LocalCommunicationTest, authenticateAgent)
{
auto authenticat_res = authenticateAgent();
EXPECT_TRUE(authenticat_res.ok());
}
TEST_F(LocalCommunicationTest, downloadManifest)
{
string new_manifest_string = "new manifest";
EXPECT_CALL(mock_orc_tools, readFile("/etc/cp/conf/offline_manifest.json")).WillOnce(Return(new_manifest_string));
GetResourceFile resourse_file(GetResourceFile::ResourceFileType::MANIFEST);
auto downloaded_string = downloadAttributeFile(resourse_file);
EXPECT_TRUE(downloaded_string.ok());
EXPECT_EQ(downloaded_string.unpack(), new_manifest_string);
}
TEST_F(LocalCommunicationTest, checkUpdateWithNoUpdate)
{
Maybe<string> manifest_checksum(string("1"));
Maybe<string> policy_checksum(string("2"));
Maybe<string> settings_checksum(string("3"));
Maybe<string> data_checksum(string("4"));
EXPECT_CALL(mock_orc_tools, calculateChecksum(
Package::ChecksumTypes::SHA256, "/etc/cp/conf/offline_manifest.json")).WillOnce(Return(manifest_checksum));
EXPECT_CALL(mock_orc_tools, calculateChecksum(
Package::ChecksumTypes::SHA256, "/etc/cp/conf/offline_policy.json")).WillOnce(Return(policy_checksum));
EXPECT_CALL(mock_orc_tools, calculateChecksum(
Package::ChecksumTypes::SHA256, "/etc/cp/conf/offline_settings.json")).WillOnce(Return(settings_checksum));
EXPECT_CALL(mock_orc_tools, calculateChecksum(
Package::ChecksumTypes::SHA256, "/etc/cp/conf/data/offline_data.json")).WillOnce(Return(data_checksum));
CheckUpdateRequest request(
*manifest_checksum,
*policy_checksum,
*settings_checksum,
*data_checksum,
I_OrchestrationTools::SELECTED_CHECKSUM_TYPE_STR,
"123"
);
auto update_response = checkUpdate(request);
EXPECT_TRUE(update_response.ok());
Maybe<string> manifest = request.getManifest();
EXPECT_FALSE(manifest.ok());
Maybe<string> policy = request.getPolicy();
EXPECT_FALSE(policy.ok());
Maybe<string> settings = request.getSettings();
EXPECT_FALSE(settings.ok());
Maybe<string> data = request.getData();
EXPECT_FALSE(data.ok());
}
TEST_F(LocalCommunicationTest, checkUpdateWithPolicyUpdate)
{
Maybe<string> manifest_checksum(string("1"));
Maybe<string> policy_checksum(string("2"));
Maybe<string> new_policy_checksum(string("22"));
Maybe<string> settings_checksum(string("3"));
Maybe<string> data_checksum(string("4"));
EXPECT_CALL(
mock_orc_tools,
calculateChecksum(Package::ChecksumTypes::SHA256, "/etc/cp/conf/offline_manifest.json")
).WillOnce(Return(manifest_checksum));
EXPECT_CALL(
mock_orc_tools,
calculateChecksum(Package::ChecksumTypes::SHA256, "/etc/cp/conf/offline_policy.json")
).WillOnce(Return(new_policy_checksum));
EXPECT_CALL(
mock_orc_tools,
calculateChecksum(Package::ChecksumTypes::SHA256, "/etc/cp/conf/offline_settings.json")
).WillOnce(Return(settings_checksum));
EXPECT_CALL(
mock_orc_tools,
calculateChecksum(Package::ChecksumTypes::SHA256, "/etc/cp/conf/data/offline_data.json")
).WillOnce(Return(data_checksum));
CheckUpdateRequest request(
*manifest_checksum,
*policy_checksum,
*settings_checksum,
*data_checksum,
I_OrchestrationTools::SELECTED_CHECKSUM_TYPE_STR,
"123"
);
auto update_response = checkUpdate(request);
EXPECT_TRUE(update_response.ok());
Maybe<string> manifest = request.getManifest();
EXPECT_FALSE(manifest.ok());
EXPECT_THAT(request.getPolicy(), IsValue("22"));
Maybe<string> settings = request.getSettings();
EXPECT_FALSE(settings.ok());
Maybe<string> data = request.getData();
EXPECT_FALSE(data.ok());
}
TEST_F(LocalCommunicationTest, setAddressExtenesion)
{
setAddressExtenesion("Test");
}
TEST_F(LocalCommunicationTest, sendPolicyVersion)
{
auto res = sendPolicyVersion("12");
EXPECT_TRUE(res.ok());
}

View File

@@ -0,0 +1,13 @@
add_library(waap
waap_component.cc
waap_component_impl.cc
first_request_object.cc
)
add_subdirectory(waap_clib)
add_subdirectory(reputation)
include_directories(include)
include_directories(reputation)
install(DIRECTORY resources DESTINATION http_transaction_handler_service USE_SOURCE_PERMISSIONS)

View File

@@ -0,0 +1,57 @@
// 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 "first_request_object.h"
#include "tag_and_enum_management.h"
FirstRequestNotificationObject::FirstRequestNotificationObject(
std::string asset_id,
std::string asset_name,
ReportIS::Severity severity
):
m_asset_id(asset_id),
m_asset_name(asset_name),
m_severity(severity)
{}
FirstRequestNotificationObject::~FirstRequestNotificationObject()
{}
void FirstRequestNotificationObject::serialize(cereal::JSONOutputArchive& ar) const
{
ar.setNextName("notificationConsumerData");
ar.startNode();
ar.setNextName("firstRequestNotificationConsumers");
ar.startNode();
ar(cereal::make_nvp("assetId", m_asset_id));
ar(cereal::make_nvp("assetName", m_asset_name));
ar(cereal::make_nvp("originalEventSeverity", TagAndEnumManagement::convertToString(m_severity)));
ar.finishNode();
ar.finishNode();
}
std::string FirstRequestNotificationObject::toString() const
{
std::stringstream ss;
{
cereal::JSONOutputArchive ar(ss);
serialize(ar);
}
return ss.str();
}
std::ostream& operator<<(std::ostream& os, const FirstRequestNotificationObject& obj)
{
return os << obj.toString();
}

View File

@@ -0,0 +1,42 @@
// 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 __FIRST_REQUEST_NOTIFICATION_OBJECT_H__
#define __FIRST_REQUEST_NOTIFICATION_OBJECT_H__
#include <string>
#include <ostream>
#include "cereal/archives/json.hpp"
#include "report/report.h"
class FirstRequestNotificationObject
{
public:
explicit FirstRequestNotificationObject(
std::string asset_id,
std::string asset_name,
ReportIS::Severity severity
);
virtual ~FirstRequestNotificationObject();
void serialize(cereal::JSONOutputArchive& ar) const;
friend std::ostream& operator<<(std::ostream& os, const FirstRequestNotificationObject& obj);
private:
std::string toString() const;
std::string m_asset_id;
std::string m_asset_name;
ReportIS::Severity m_severity;
};
#endif

View File

@@ -0,0 +1,30 @@
// 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.
#pragma once
#define BACKUP_DIRECTORY_PATH "/etc/cp/conf/waap/"
// reduce from 2048 in order to accomodate in 10K max log size in Kibana
#define MAX_LOG_FIELD_SIZE 1536
// maximum bytes response body log field size can reduce from request body log
#define MIN_RESP_BODY_LOG_FIELD_SIZE (std::size_t{500})
// size of clean values LRU cache
#define SIGS_APPLY_CLEAN_CACHE_CAPACITY 4096
// size of suspicious values LRU cache
#define SIGS_APPLY_SUSPICIOUS_CACHE_CAPACITY 4096
// size of SampleType cache capacity
#define SIGS_SAMPLE_TYPE_CACHE_CAPACITY 4096
// ScoreBuilder pool names
#define KEYWORDS_SCORE_POOL_BASE "base_scores"
#define KEYWORDS_SCORE_POOL_HEADERS "headers_scores"

View File

@@ -0,0 +1,24 @@
// 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.
#pragma once
class IWaf2Transaction;
class IWaapConfig;
struct AnalysisResult;
class I_DeepAnalyzer {
public:
virtual AnalysisResult analyzeData(IWaf2Transaction* waf2Trans, const IWaapConfig* pSitePolicy) = 0;
virtual ~I_DeepAnalyzer() {};
};

View File

@@ -0,0 +1,26 @@
// 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 __I_IGNORE_SOURCES_H__
#define __I_IGNORE_SOURCES_H__
#include <vector>
#include <string>
class I_IgnoreSources
{
public:
virtual std::vector<std::string>* getSourcesToIgnore() = 0;
virtual bool ready() = 0;
};
#endif

View 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.
#pragma once
#include <string>
#include "../waap_clib/WaapKeywords.h"
#include "i_serialize.h"
#include <unordered_set>
#include <vector>
class IWaf2Transaction;
class I_IndicatorsFilter{
public:
virtual ~I_IndicatorsFilter() { }
// filters indicators from keywords vector
virtual void filterKeywords(
const std::string &key,
Waap::Keywords::KeywordsSet& keywords,
Waap::Keywords::KeywordsVec& filteredKeywords) = 0;
// register keyword for a specific key
virtual void registerKeywords(const std::string& key, Waap::Keywords::KeywordsSet& keyword,
IWaf2Transaction* pTransaction) = 0;
// returns true if the keyword based on the key should be filtered out
virtual bool shouldFilterKeyword(const std::string &key, const std::string &keyword) const = 0;
};

View File

@@ -0,0 +1,281 @@
// 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.
#pragma once
#include <chrono>
#include <fstream>
#include "i_time_get.h"
#include "i_encryptor.h"
#include "rest.h"
#include "i_messaging.h"
#include "i_mainloop.h"
#include "i_agent_details.h"
static const uint max_send_obj_retries = 3;
USE_DEBUG_FLAG(D_WAAP);
class RestGetFile : public ClientRest
{
public:
// decrypts and load json
bool loadJson(const std::string& json);
// gen json and encrypt
Maybe<std::string> genJson() const;
};
struct FileMetaData
{
std::string filename;
std::string modified;
};
class RemoteFilesList : public ClientRest
{
public:
RemoteFilesList();
// parses xml instead of json
// extracts a file list in <Contents><Key>
bool loadJson(const std::string& xml);
const std::vector<FileMetaData>& getFilesMetadataList() const;
const std::vector<std::string>& getFilesList() const;
private:
RestParam<std::vector<FileMetaData>> files;
std::vector<std::string> filesPathsList;
};
class I_Serializable {
public:
virtual void serialize(std::ostream& stream) = 0;
virtual void deserialize(std::istream& stream) = 0;
};
class I_RemoteSyncSerialize {
public:
virtual bool postData() = 0;
virtual void pullData(const std::vector<std::string>& files) = 0;
virtual void processData() = 0;
virtual void postProcessedData() = 0;
virtual void pullProcessedData(const std::vector<std::string>& files) = 0;
virtual void updateState(const std::vector<std::string>& files) = 0;
};
class I_Backup {
public:
// open stream and serialize data
virtual void saveData() = 0;
// open stream and deserialize data
virtual void restore() = 0;
};
class SerializeToFileBase :
public I_Backup,
public I_Serializable
{
public:
SerializeToFileBase(std::string filePath);
virtual ~SerializeToFileBase();
virtual void saveData();
virtual void restore();
virtual void setFilePath(const std::string &new_file_path);
protected:
// saved file name for testing
std::string m_filePath;
private:
void loadFromFile(std::string filePath);
};
class SerializeToFilePeriodically : public SerializeToFileBase
{
public:
SerializeToFilePeriodically(std::chrono::seconds pollingIntervals, std::string filePath);
virtual ~SerializeToFilePeriodically();
void setInterval(std::chrono::seconds newInterval);
protected:
void backupWorker();
private:
std::chrono::microseconds m_lastSerialization;
std::chrono::seconds m_interval;
};
class WaapComponent;
class SerializeToLocalAndRemoteSyncBase : public I_RemoteSyncSerialize, public SerializeToFileBase
{
public:
SerializeToLocalAndRemoteSyncBase(std::chrono::minutes interval,
std::chrono::seconds waitForSync,
const std::string& filePath,
const std::string& remotePath,
const std::string& assetId,
const std::string& owner);
virtual ~SerializeToLocalAndRemoteSyncBase();
virtual void restore();
virtual void syncWorker();
void setInterval(std::chrono::seconds newInterval);
std::chrono::seconds getIntervalDuration() const;
void setRemoteSyncEnabled(bool enabled);
protected:
void mergeProcessedFromRemote();
std::string getPostDataUrl();
std::string getUri();
size_t getIntervalsCount();
template<typename T>
bool sendObject(T &obj, I_Messaging::Method method, std::string uri)
{
I_Messaging *messaging = Singleton::Consume<I_Messaging>::by<WaapComponent>();
I_AgentDetails *agentDetails = Singleton::Consume<I_AgentDetails>::by<WaapComponent>();
if (agentDetails->getOrchestrationMode() == OrchestrationMode::OFFLINE) {
dbgDebug(D_WAAP) << "offline mode not sending object";
return false;
}
if (agentDetails->getOrchestrationMode() == OrchestrationMode::HYBRID) {
Flags <MessageConnConfig> conn_flags;
conn_flags.setFlag(MessageConnConfig::EXTERNAL);
std::string tenant_header = "X-Tenant-Id: " + agentDetails->getTenantId();
return messaging->sendObject(
obj,
method,
getSharedStorageHost(),
80,
conn_flags,
uri,
tenant_header,
nullptr,
MessageTypeTag::WAAP_LEARNING);
}
return messaging->sendObject(
obj,
method,
uri,
"",
nullptr,
true,
MessageTypeTag::WAAP_LEARNING);
}
template<typename T>
bool sendObjectWithRetry(T &obj, I_Messaging::Method method, std::string uri)
{
I_MainLoop *mainloop = Singleton::Consume<I_MainLoop>::by<WaapComponent>();
for (uint i = 0; i < max_send_obj_retries; i++)
{
if (sendObject(obj, method, uri))
{
dbgTrace(D_WAAP) <<
"object sent successfully after " << i << " retry attempts";
return true;
}
dbgWarning(D_WAAP) << "Failed to send object. Attempt: " << i;
mainloop->yield(true);
}
dbgError(D_WAAP) << "Failed to send object, reached maximum attempts: " <<
max_send_obj_retries;
return false;
}
template<typename T>
bool sendNoReplyObject(T &obj, I_Messaging::Method method, std::string uri)
{
I_Messaging *messaging = Singleton::Consume<I_Messaging>::by<WaapComponent>();
I_AgentDetails *agentDetails = Singleton::Consume<I_AgentDetails>::by<WaapComponent>();
if (agentDetails->getOrchestrationMode() == OrchestrationMode::OFFLINE) {
dbgDebug(D_WAAP) << "offline mode not sending object";
return false;
}
if (agentDetails->getOrchestrationMode() == OrchestrationMode::HYBRID) {
Flags<MessageConnConfig> conn_flags;
conn_flags.setFlag(MessageConnConfig::EXTERNAL);
std::string tenant_header = "X-Tenant-Id: " + agentDetails->getTenantId();
return messaging->sendNoReplyObject(
obj,
method,
getSharedStorageHost(),
80,
conn_flags,
uri,
tenant_header,
nullptr,
MessageTypeTag::WAAP_LEARNING);
}
return messaging->sendNoReplyObject(
obj,
method,
uri,
"",
nullptr,
true,
MessageTypeTag::WAAP_LEARNING);
}
template<typename T>
bool sendNoReplyObjectWithRetry(T &obj, I_Messaging::Method method, std::string uri)
{
I_MainLoop *mainloop= Singleton::Consume<I_MainLoop>::by<WaapComponent>();
for (uint i = 0; i < max_send_obj_retries; i++)
{
if (sendNoReplyObject(obj, method, uri))
{
dbgTrace(D_WAAP) <<
"object sent successfully after " << i << " retry attempts";
return true;
}
dbgWarning(D_WAAP) << "Failed to send object. Attempt: " << i;
mainloop->yield(true);
}
dbgError(D_WAAP) << "Failed to send object, reached maximum attempts: " <<
max_send_obj_retries;
return false;
}
const std::string m_remotePath; // Created from tenentId + / + assetId + / + class
std::chrono::seconds m_interval;
std::string m_owner;
private:
bool localSyncAndProcess();
void updateStateFromRemoteService();
RemoteFilesList getProcessedFilesList();
RemoteFilesList getRemoteProcessedFilesList();
std::string getWindowId();
bool isBase();
std::string getLearningHost();
std::string getSharedStorageHost();
I_MainLoop* m_pMainLoop;
std::chrono::microseconds m_waitForSync;
uint m_workerRoutineId;
size_t m_daysCount;
size_t m_windowsCount;
size_t m_intervalsCounter;
bool m_remoteSyncEnabled;
const std::string m_assetId;
std::string m_type;
std::string m_lastProcessedModified;
Maybe<std::string> m_shared_storage_host;
Maybe<std::string> m_learning_host;
};

View File

@@ -0,0 +1,144 @@
// 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.
#pragma once
#include "../waap_clib/WaapDecision.h"
#include "../include/WaapDefines.h"
#include "../waap_clib/Csrf.h"
#include "../waap_clib/Waf2Util.h"
#include "../waap_clib/WaapOpenRedirect.h"
#include "../waap_clib/FpMitigation.h"
#include "../waap_clib/DeepParser.h"
#include "http_inspection_events.h"
enum HeaderType {
UNKNOWN_HEADER,
HOST_HEADER,
USER_AGENT_HEADER,
COOKIE_HEADER,
REFERER_HEADER,
CONTENT_TYPE_HEADER,
CLEAN_HEADER,
OTHER_KNOWN_HEADERS
};
struct AnalysisResult;
class WaapAssetState;
struct Waf2TransactionFlags {
bool endResponseHeadersCalled;
bool requestDataPushStarted;
bool responseDataPushStarted;
Waf2TransactionFlags():
endResponseHeadersCalled(false),
requestDataPushStarted(false),
responseDataPushStarted(false)
{
}
};
class IWaf2Transaction {
public:
virtual ~IWaf2Transaction() {}
virtual uint64_t getIndex() const = 0;
virtual void setIndex(uint64_t index) = 0;
virtual std::shared_ptr<WaapAssetState> getAssetState() = 0;
virtual IWaapConfig* getSiteConfig() = 0;
virtual DeepParser& getDeepParser() = 0;
virtual bool get_ignoreScore() const = 0;
virtual void addNote(const std::string &note) = 0;
virtual bool shouldIgnoreOverride(const Waf2ScanResult &res) = 0;
virtual bool reportScanResult(const Waf2ScanResult &res) = 0;
virtual const std::string getHost() const = 0;
virtual Waap::OpenRedirect::State &getOpenRedirectState() = 0;
virtual const std::string getLocation() const = 0;
virtual const std::string getUserAgent() const = 0;
virtual const std::string getParam() const = 0;
virtual const std::vector<std::string> getKeywordMatches() const = 0;
virtual const std::vector<std::string> getKeywordsCombinations() const = 0;
virtual const std::string getContentTypeStr() const = 0;
virtual Waap::Util::ContentType getContentType() const = 0;
virtual const std::string getKeywordMatchesStr() const = 0;
virtual const std::string getSample() const = 0;
virtual const std::string getLastScanSample() const = 0;
virtual const std::string& getLastScanParamName() const = 0;
virtual const std::string getMethod() const = 0;
virtual const std::string getHdrContent(std::string hdrName) const = 0;
virtual const WaapDecision &getWaapDecision() const = 0;
virtual const std::string& getRemoteAddr() const = 0;
virtual const std::string getUri() const = 0;
virtual const std::string getUriStr() const = 0;
virtual const std::string& getSourceIdentifier() const = 0;
virtual double getScore() const = 0;
virtual const std::vector<double> getScoreArray() const = 0;
virtual Waap::CSRF::State& getCsrfState() = 0;
virtual ngx_http_cp_verdict_e getUserLimitVerdict() = 0;
virtual const std::string getUserLimitVerdictStr() const = 0;
virtual const std::string getViolatedUserLimitTypeStr() const = 0;
virtual void checkShouldInject() = 0;
virtual void completeInjectionResponseBody(std::string& strInjection) = 0;
virtual void sendLog() = 0;
virtual bool decideAfterHeaders() = 0;
virtual int decideFinal(
int mode,
AnalysisResult &transactionResult,
const std::string &poolName=KEYWORDS_SCORE_POOL_BASE,
PolicyCounterType fpClassification = UNKNOWN_TYPE) = 0;
virtual bool decideResponse() = 0;
virtual void clearAllInjectionReasons() = 0;
virtual bool shouldInspectResponse() = 0;
virtual bool shouldInjectResponse() = 0;
virtual bool shouldInjectCSRF() = 0;
virtual bool shouldInjectSecurityHeaders() = 0;
virtual void handleSecurityHeadersInjection(
std::vector<std::pair<std::string, std::string>>& injectHeaderStrs) = 0;
virtual void disableShouldInjectSecurityHeaders() = 0;
virtual void handleCsrfHeaderInjection(std::string& injectStr) = 0;
virtual bool findHtmlTagToInject(const char* data, int data_len, int& pos) = 0;
virtual bool isHtmlType(const char* data, int data_len) = 0;
virtual HeaderType detectHeaderType(const char* name, int name_len) = 0;
virtual void start() = 0;
virtual void set_transaction_time(const char* log_time) = 0;
virtual void set_transaction_remote(const char* remote_addr, int remote_port) = 0;
virtual void set_transaction_local(const char* local_addr, int local_port) = 0;
// Request
virtual void set_method(const char* method) = 0;
virtual void set_uri(const char* uri) = 0;
virtual void start_request_hdrs() = 0;
virtual void add_request_hdr(const char* name, int name_len, const char* value, int value_len) = 0;
virtual void end_request_hdrs() = 0;
virtual void start_request_body() = 0;
virtual void add_request_body_chunk(const char* data, int data_len) = 0;
virtual void end_request_body() = 0;
virtual void end_request() = 0;
// Response
virtual void start_response(int response_status, int http_version) = 0;
virtual void start_response_hdrs() = 0;
virtual void add_response_hdr(const char* name, int name_len, const char* value, int value_len) = 0;
virtual void end_response_hdrs() = 0;
virtual void start_response_body() = 0;
virtual void add_response_body_chunk(const char* data, int data_len) = 0;
virtual void end_response_body() = 0;
virtual void end_response() = 0;
virtual void collectFoundPatterns() = 0;
virtual ReportIS::Severity computeEventSeverityFromDecision() const = 0;
virtual void finish() = 0;
virtual Waf2TransactionFlags &getTransactionFlags() = 0;
};

View File

@@ -0,0 +1,69 @@
// 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.
#pragma once
#include "../waap_clib/WaapOverride.h"
#include "../waap_clib/WaapTrigger.h"
#include "../waap_clib/TrustedSources.h"
#include "../waap_clib/WaapParameters.h"
#include "../waap_clib/WaapOpenRedirectPolicy.h"
#include "../waap_clib/WaapErrorDisclosurePolicy.h"
#include "../waap_clib/CsrfPolicy.h"
#include "../waap_clib/UserLimitsPolicy.h"
#include "../waap_clib/RateLimiting.h"
#include "../waap_clib/SecurityHeadersPolicy.h"
#include <memory>
enum class BlockingLevel {
NO_BLOCKING = 0,
LOW_BLOCKING_LEVEL,
MEDIUM_BLOCKING_LEVEL,
HIGH_BLOCKING_LEVEL
};
enum class AttackMitigationMode
{
DISABLED = 0,
LEARNING,
PREVENT,
UNKNOWN
};
class IWaapConfig {
public:
virtual const std::string& get_AssetId() const = 0;
virtual const std::string& get_AssetName() const = 0;
virtual const BlockingLevel& get_BlockingLevel() const = 0;
virtual const std::string& get_PracticeId() const = 0;
virtual const std::string& get_PracticeName() const = 0;
virtual const std::string& get_PracticeSubType() const = 0;
virtual const std::string& get_RuleId() const = 0;
virtual const std::string& get_RuleName() const = 0;
virtual const bool& get_WebAttackMitigation() const = 0;
virtual const std::string& get_WebAttackMitigationAction() const = 0;
virtual const std::shared_ptr<Waap::Override::Policy>& get_OverridePolicy() const = 0;
virtual const std::shared_ptr<Waap::Trigger::Policy>& get_TriggerPolicy() const = 0;
virtual const std::shared_ptr<Waap::TrustedSources::TrustedSourcesParameter>& get_TrustedSourcesPolicy() const = 0;
virtual const std::shared_ptr<Waap::Parameters::WaapParameters>& get_WaapParametersPolicy() const = 0;
virtual const std::shared_ptr<Waap::OpenRedirect::Policy>& get_OpenRedirectPolicy() const = 0;
virtual const std::shared_ptr<Waap::ErrorDisclosure::Policy>& get_ErrorDisclosurePolicy() const = 0;
virtual const std::shared_ptr<Waap::Csrf::Policy>& get_CsrfPolicy() const = 0;
virtual const std::shared_ptr<Waap::RateLimiting::Policy>& get_RateLimitingPolicy() const = 0;
virtual const std::shared_ptr<Waap::RateLimiting::Policy>& get_ErrorLimitingPolicy() const = 0;
virtual const std::shared_ptr<Waap::SecurityHeaders::Policy>& get_SecurityHeadersPolicy() const = 0;
virtual const std::shared_ptr<Waap::UserLimits::Policy>& get_UserLimitsPolicy() const = 0;
virtual void printMe(std::ostream& os) const = 0;
};

Some files were not shown because too many files have changed in this diff Show More