diff --git a/components/include/central_nginx_manager.h b/components/include/central_nginx_manager.h new file mode 100755 index 0000000..5c3d134 --- /dev/null +++ b/components/include/central_nginx_manager.h @@ -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 __CENTRAL_NGINX_MANAGER_H__ +#define __CENTRAL_NGINX_MANAGER_H__ + +#include "component.h" +#include "singleton.h" +#include "i_messaging.h" +#include "i_rest_api.h" +#include "i_mainloop.h" +#include "i_agent_details.h" + +class CentralNginxManager + : + public Component, + Singleton::Consume, + Singleton::Consume, + Singleton::Consume, + Singleton::Consume +{ +public: + CentralNginxManager(); + ~CentralNginxManager(); + + void preload() override; + void init() override; + void fini() override; + +private: + class Impl; + std::unique_ptr pimpl; +}; + +#endif // __CENTRAL_NGINX_MANAGER_H__ diff --git a/components/include/nginx_message_reader.h b/components/include/nginx_message_reader.h new file mode 100755 index 0000000..19b7fb9 --- /dev/null +++ b/components/include/nginx_message_reader.h @@ -0,0 +1,28 @@ +#ifndef __NGINX_MESSAGE_READER_H__ +#define __NGINX_MESSAGE_READER_H__ + +#include "singleton.h" +#include "i_mainloop.h" +#include "i_socket_is.h" +#include "component.h" + +class NginxMessageReader + : + public Component, + Singleton::Consume, + Singleton::Consume +{ +public: + NginxMessageReader(); + ~NginxMessageReader(); + + void init() override; + void fini() override; + void preload() override; + +private: + class Impl; + std::unique_ptr pimpl; +}; + +#endif //__NGINX_MESSAGE_READER_H__ diff --git a/components/include/nginx_utils.h b/components/include/nginx_utils.h new file mode 100755 index 0000000..7f434e2 --- /dev/null +++ b/components/include/nginx_utils.h @@ -0,0 +1,51 @@ +// 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 __NGINX_UTILS_H__ +#define __NGINX_UTILS_H__ + +#include + +#include "maybe_res.h" +#include "singleton.h" +#include "i_shell_cmd.h" + +class NginxConfCollector +{ +public: + NginxConfCollector(const std::string &nginx_conf_input_path, const std::string &nginx_conf_output_path); + Maybe generateFullNginxConf() const; + +private: + std::vector expandIncludes(const std::string &includePattern) const; + void processConfigFile( + const std::string &path, + std::ostringstream &conf_output, + std::vector &errors + ) const; + + std::string main_conf_input_path; + std::string main_conf_output_path; + std::string main_conf_directory_path; +}; + +class NginxUtils : Singleton::Consume +{ +public: + static std::string getModulesPath(); + static std::string getMainNginxConfPath(); + static Maybe validateNginxConf(const std::string &nginx_conf_path); + static Maybe reloadNginx(const std::string &nginx_conf_path); +}; + +#endif // __NGINX_UTILS_H__ diff --git a/components/nginx_message_reader/CMakeLists.txt b/components/nginx_message_reader/CMakeLists.txt new file mode 100755 index 0000000..ec26913 --- /dev/null +++ b/components/nginx_message_reader/CMakeLists.txt @@ -0,0 +1,3 @@ +link_directories(${BOOST_ROOT}/lib) + +add_library(nginx_message_reader nginx_message_reader.cc) diff --git a/components/nginx_message_reader/nginx_message_reader.cc b/components/nginx_message_reader/nginx_message_reader.cc new file mode 100755 index 0000000..a9c4f31 --- /dev/null +++ b/components/nginx_message_reader/nginx_message_reader.cc @@ -0,0 +1,735 @@ +#include "nginx_message_reader.h" + +#include +#include +#include +#include + +#include "config.h" +#include "singleton.h" +#include "i_mainloop.h" +#include "enum_array.h" +#include "log_generator.h" +#include "maybe_res.h" +#include "http_transaction_data.h" +#include "generic_rulebase/rulebase_config.h" +#include "generic_rulebase/evaluators/asset_eval.h" +#include "generic_rulebase/triggers_config.h" +#include "agent_core_utilities.h" +#include "rate_limit_config.h" + +USE_DEBUG_FLAG(D_NGINX_MESSAGE_READER); + +using namespace std; + +static const string syslog_regex_string = ( + "<[0-9]+>([A-Z][a-z][a-z]\\s{1,2}\\d{1,2}\\s\\d{2}" + "[:]\\d{2}[:]\\d{2})\\s([\\w][\\w\\d\\.@-]*)\\s(nginx:)" +); + +static const boost::regex socket_address_regex("(\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)"); +static const boost::regex syslog_regex(syslog_regex_string); +static const boost::regex alert_log_regex( + "(" + + syslog_regex_string + ") " + + "(.+?\\[alert\\] )(.+?)" + ", (client: .+?)" + ", (server: .+?)" + ", (request: \".+?\")" + ", (upstream: \".+?\")" + ", (host: \".+?\")$" +); + +static const boost::regex error_log_regex( + "(" + + syslog_regex_string + ") " + + "(.+?\\[error\\] )(.+?)" + ", (client: .+?)" + ", (server: .+?)" + ", (request: \".+?\")" + ", (upstream: \".+?\")" + ", (host: \".+?\")$" +); + +static const boost::regex server_regex("(\\d+\\.\\d+\\.\\d+\\.\\d+)|(\\w+\\.\\w+)"); +static const boost::regex uri_regex("^/"); +static const boost::regex port_regex("\\d+"); +static const boost::regex response_code_regex("[0-9]{3}"); +static const boost::regex http_method_regex("[A-Za-z]+"); + +class NginxMessageReader::Impl +{ +public: + void + init() + { + dbgFlow(D_NGINX_MESSAGE_READER); + I_MainLoop *mainloop = Singleton::Consume::by(); + mainloop->addOneTimeRoutine( + I_MainLoop::RoutineType::System, + [this] () + { + initSyslogServerSocket(); + handleNginxLogs(); + }, + "Initialize nginx syslog", + true + ); + } + + void + preload() + { + registerConfigLoadCb([this]() { loadNginxMessageReaderConfig(); }); + } + + void + fini() + { + I_Socket *i_socket = Singleton::Consume::by(); + i_socket->closeSocket(syslog_server_socket); + } + + void + loadNginxMessageReaderConfig() + { + rate_limit_status_code = getProfileAgentSettingWithDefault( + "429", + "accessControl.rateLimit.returnCode" + ); + dbgTrace(D_NGINX_MESSAGE_READER) << "Selected rate-limit status code: " << rate_limit_status_code; + } + +private: + enum class LogInfo { + HTTP_METHOD, + URI, + RESPONSE_CODE, + HOST, + SOURCE, + DESTINATION_IP, + DESTINATION_PORT, + EVENT_MESSAGE, + ASSET_ID, + ASSET_NAME, + RULE_NAME, + RULE_ID, + COUNT + }; + + void + initSyslogServerSocket() + { + dbgFlow(D_NGINX_MESSAGE_READER); + I_MainLoop *mainloop = Singleton::Consume::by(); + I_Socket *i_socket = Singleton::Consume::by(); + string nginx_syslog_server_address = getProfileAgentSettingWithDefault( + "127.0.0.1:1514", + "reverseProxy.nginx.syslogAddress" + ); + dbgInfo(D_NGINX_MESSAGE_READER) << "Attempting to open a socket: " << nginx_syslog_server_address; + do { + Maybe new_socket = i_socket->genSocket( + I_Socket::SocketType::UDP, + false, + true, + nginx_syslog_server_address + ); + if (!new_socket.ok()) { + dbgError(D_NGINX_MESSAGE_READER) << "Failed to open a socket. Error: " << new_socket.getErr(); + mainloop->yield(chrono::milliseconds(500)); + continue; + } + + if (new_socket.unpack() < 0) { + dbgError(D_NGINX_MESSAGE_READER)<< "Generated socket is OK yet negative"; + mainloop->yield(chrono::milliseconds(500)); + continue; + } + syslog_server_socket = new_socket.unpack(); + dbgInfo(D_NGINX_MESSAGE_READER) + << "Opened socket for nginx logs over syslog. Socket: " + << syslog_server_socket; + } while (syslog_server_socket < 0); + } + + void + handleNginxLogs() + { + dbgFlow(D_NGINX_MESSAGE_READER); + I_MainLoop::Routine read_logs = + [this] () + { + Maybe logs = getLogsFromSocket(syslog_server_socket); + + if (!logs.ok()) { + dbgWarning(D_NGINX_MESSAGE_READER) + << "Failed to get NGINX logs from the socket. Error: " + << logs.getErr(); + return; + } + string raw_logs_to_parse = logs.unpackMove(); + vector logs_to_parse = separateLogs(raw_logs_to_parse); + + for (auto const &log: logs_to_parse) { + bool log_sent; + if (isAccessLog(log)) { + log_sent = sendAccessLog(log); + } else if (isAlertErrorLog(log) || isErrorLog(log)) { + log_sent = sendErrorLog(log); + } else { + dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format"; + continue; + } + if (!log_sent) { + dbgWarning(D_NGINX_MESSAGE_READER) << "Failed to send Log to Infinity Portal"; + } else { + dbgTrace(D_NGINX_MESSAGE_READER) << "Succesfully sent nginx log to Infinity Portal"; + } + } + }; + I_MainLoop *mainloop = Singleton::Consume::by(); + mainloop->addFileRoutine( + I_MainLoop::RoutineType::RealTime, + syslog_server_socket, + read_logs, + "Process nginx logs", + true + ); + } + + bool + sendAccessLog(const string &log) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Access log" << log; + Maybe> log_info = parseAccessLog(log); + if (!log_info.ok()) { + dbgWarning(D_NGINX_MESSAGE_READER) + << "Failed parsing the NGINX logs. Error: " + << log_info.getErr(); + return false; + } + auto unpacked_log_info = log_info.unpack(); + + if (unpacked_log_info[LogInfo::RESPONSE_CODE] == rate_limit_status_code) { + return sendRateLimitLog(unpacked_log_info); + } + return sendLog(unpacked_log_info); + } + + bool + sendErrorLog(const string &log) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Error log" << log; + Maybe> log_info = parseErrorLog(log); + if (!log_info.ok()) { + dbgWarning(D_NGINX_MESSAGE_READER) + << "Failed parsing the NGINX logs. Error: " + << log_info.getErr(); + return false; + } + return sendLog(log_info.unpack()); + } + + bool + isAccessLog(const string &log) const + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Chekck if string contains \"accessLog\"" << log; + return log.find("accessLog") != string::npos; + } + + bool + isAlertErrorLog(const string &log) const + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Check if log is of type 'error log'. Log: " << log; + return log.find("[alert]") != string::npos; + } + + bool + isErrorLog(const string &log) const + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Check if log is of type 'error log'. Log: " << log; + return log.find("[error]") != string::npos; + } + + bool + sendLog(const EnumArray &log_info) + { + dbgFlow(D_NGINX_MESSAGE_READER); + string event_name; + switch (log_info[LogInfo::RESPONSE_CODE][0]) { + case '4': { + event_name = "Invalid request or incorrect reverse proxy configuration - Request dropped." + " Please check the reverse proxy configuration of your relevant assets"; + break; + } + case '5': { + event_name = "AppSec Gateway reverse proxy error - Request dropped. " + "Please verify the reverse proxy configuration of your relevant assets. " + "If the issue persists please contact Check Point Support"; + break; + } + default: { + dbgError(D_NGINX_MESSAGE_READER) << "Irrelevant status code"; + return false; + } + } + + dbgTrace(D_NGINX_MESSAGE_READER) + << "Nginx log's event name and response code: " + << event_name + << ", " + << log_info[LogInfo::RESPONSE_CODE]; + LogGen log( + event_name, + ReportIS::Audience::SECURITY, + ReportIS::Severity::INFO, + ReportIS::Priority::LOW, + ReportIS::Tags::REVERSE_PROXY + ); + log << LogField("eventConfidence", "High"); + + for (LogInfo field : makeRange()) { + Maybe string_field = convertLogFieldToString(field); + if (!string_field.ok()) { + dbgDebug(D_NGINX_MESSAGE_READER) << "Enum field was not converted: " << string_field.getErr(); + return false; + } + + if (field != LogInfo::DESTINATION_PORT) { + log << LogField(string_field.unpack(), log_info[field]); + continue; + } + + try { + log << LogField(string_field.unpack(), stoi(log_info[field])); + } catch (const exception &e) { + dbgError(D_NGINX_MESSAGE_READER) + << "Unable to convert port to numeric value: " + << e.what(); + log << LogField(string_field.unpack(), 0); + } + } + return true; + } + + bool + sendRateLimitLog(const EnumArray &log_info) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Getting rate-limit rules of asset ID: " << log_info[LogInfo::ASSET_ID]; + + ScopedContext rate_limit_ctx; + + rate_limit_ctx.registerValue(AssetMatcher::ctx_key, log_info[LogInfo::ASSET_ID]); + auto rate_limit_config = getConfiguration("rulebase", "rateLimit"); + if (!rate_limit_config.ok()) { + dbgTrace(D_NGINX_MESSAGE_READER) + << "Rate limit context does not match asset ID: " << log_info[LogInfo::ASSET_ID]; + return false; + } + RateLimitConfig unpacked_rate_limit_config = rate_limit_config.unpack(); + + string nginx_uri = log_info[LogInfo::URI]; + const LogTriggerConf &rate_limit_trigger = unpacked_rate_limit_config.getRateLimitTrigger(nginx_uri); + + dbgTrace(D_NGINX_MESSAGE_READER)<< "About to generate NGINX rate-limit log"; + + string event_name = "Rate limit"; + string security_action = "Drop"; + bool is_log_required = false; + + // Prevent events checkbox (in triggers) + if (rate_limit_trigger.isPreventLogActive(LogTriggerConf::SecurityType::AccessControl)) { + is_log_required = true; + } + + if (!is_log_required) { + dbgTrace(D_NGINX_MESSAGE_READER) << "Not sending NGINX rate-limit log as it is not required"; + return false; + } + + ostringstream src_ip; + ostringstream dst_ip; + src_ip << log_info[LogInfo::SOURCE]; + dst_ip << log_info[LogInfo::DESTINATION_IP]; + + ReportIS::Severity log_severity = ReportIS::Severity::MEDIUM; + ReportIS::Priority log_priority = ReportIS::Priority::MEDIUM; + + LogGen log = rate_limit_trigger( + event_name, + LogTriggerConf::SecurityType::AccessControl, + log_severity, + log_priority, + true, // is drop + LogField("practiceType", "Rate Limit"), + ReportIS::Tags::RATE_LIMIT + ); + + for (LogInfo field : makeRange()) { + Maybe string_field = convertLogFieldToString(field); + if (!string_field.ok()) { + dbgDebug(D_NGINX_MESSAGE_READER) << "Enum field was not converted: " << string_field.getErr(); + return false; + } + + if ( + field == LogInfo::HOST || + field == LogInfo::URI || + field == LogInfo::HTTP_METHOD || + field == LogInfo::SOURCE || + field == LogInfo::DESTINATION_IP || + field == LogInfo::ASSET_ID || + field == LogInfo::ASSET_NAME || + field == LogInfo::RESPONSE_CODE + ) { + if (!log_info[field].empty()) { + log << LogField(string_field.unpack(), log_info[field]); + continue; + } + } + + if (field == LogInfo::DESTINATION_PORT) { + try { + int numeric_dst_port = stoi(log_info[field]); + log << LogField(string_field.unpack(), numeric_dst_port); + } catch (const exception &e) { + dbgWarning(D_NGINX_MESSAGE_READER) + << "Unable to convert dst port: " + << log_info[field] + << " to numberic value. Error: " + << e.what(); + } + } + } + + return true; + } + + Maybe + convertLogFieldToString(LogInfo field) + { + dbgFlow(D_NGINX_MESSAGE_READER); + switch (field) { + case LogInfo::HTTP_METHOD: + return string("httpMethod"); + case LogInfo::URI: + return string("httpUriPath"); + case LogInfo::RESPONSE_CODE: + return string("httpResponseCode"); + case LogInfo::HOST: + return string("httpHostName"); + case LogInfo::SOURCE: + return string("httpSourceId"); + case LogInfo::DESTINATION_IP: + return string("destinationIp"); + case LogInfo::DESTINATION_PORT: + return string("destinationPort"); + case LogInfo::ASSET_ID: + return string("assetId"); + case LogInfo::ASSET_NAME: + return string("assetName"); + case LogInfo::EVENT_MESSAGE: + return string("httpResponseBody"); + case LogInfo::RULE_ID: + return string("ruleId"); + case LogInfo::RULE_NAME: + return string("ruleName"); + case LogInfo::COUNT: + dbgError(D_NGINX_MESSAGE_READER) << "LogInfo::COUNT is not allowed"; + return genError("LogInfo::COUNT is not allowed"); + } + dbgError(D_NGINX_MESSAGE_READER) << "No Enum found, int value: " << static_cast(field); + return genError("No Enum found"); + } + + static vector + separateLogs(const string &raw_logs_to_parse) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "separating logs. logs: " << raw_logs_to_parse; + dbgTrace(D_NGINX_MESSAGE_READER) << "separateLogs start of function. Logs to parse: " << raw_logs_to_parse; + boost::smatch matcher; + vector logs; + + if (raw_logs_to_parse.empty()) return logs; + + size_t pos = 0; + while (NGEN::Regex::regexSearch(__FILE__, __LINE__, raw_logs_to_parse.substr(pos), matcher, syslog_regex)) { + if (pos == 0) { + dbgTrace(D_NGINX_MESSAGE_READER) << "separateLogs pos = 0"; + pos++; + continue; + } + auto log_length = matcher.position(); + logs.push_back(raw_logs_to_parse.substr(pos - 1, log_length)); + + pos += log_length + 1; + } + logs.push_back(raw_logs_to_parse.substr(pos - 1)); + dbgTrace(D_NGINX_MESSAGE_READER) << "separateLogs end of function"; + + return logs; + } + + static pair + parseErrorLogRequestField(const string &request) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "parsing request field. request: " << request; + string formatted_request = request; + vector result; + boost::erase_all(formatted_request, "\""); + boost::erase_all(formatted_request, "\n"); + boost::split(result, formatted_request, boost::is_any_of(" "), boost::token_compress_on); + + const int http_method_index = 1; + const int uri_index = 2; + return pair(result[http_method_index], result[uri_index]); + } + + static string + parseErrorLogField(const string &field) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "parsing error log field " << field; + string formatted_field = field; + vector result; + boost::erase_all(formatted_field, "\""); + boost::erase_all(formatted_field, "\n"); + boost::split(result, formatted_field, boost::is_any_of(" "), boost::token_compress_on); + + const int field_index = 1; + return result[field_index]; + } + + void + addContextFieldsToLogInfo(EnumArray &log_info) + { + dbgFlow(D_NGINX_MESSAGE_READER); + ScopedContext ctx; + + try { + ctx.registerValue( + HttpTransactionData::listening_port_ctx, + static_cast(stoi(log_info[LogInfo::DESTINATION_PORT])) + ); + } catch (const exception &e) { + dbgError(D_NGINX_MESSAGE_READER) << "Failed register values for context " << e.what(); + } + ctx.registerValue(HttpTransactionData::host_name_ctx, log_info[LogInfo::HOST]); + ctx.registerValue(HttpTransactionData::uri_ctx, log_info[LogInfo::URI]); + auto rule_by_ctx = getConfiguration("rulebase", "rulesConfig"); + if (!rule_by_ctx.ok()) { + dbgWarning(D_NGINX_MESSAGE_READER) + << "AssetId was not found by the given context. Reason: " + << rule_by_ctx.getErr(); + return; + } + + BasicRuleConfig context = rule_by_ctx.unpack(); + log_info[LogInfo::ASSET_ID] = context.getAssetId(); + log_info[LogInfo::ASSET_NAME] = context.getAssetName(); + log_info[LogInfo::RULE_ID] = context.getRuleId(); + log_info[LogInfo::RULE_NAME] = context.getRuleName(); + } + + Maybe> + parseErrorLog(const string &log_line) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Handling log line:" << log_line; + string port; + EnumArray log_info(EnumArray::Fill(), string("")); + + boost::smatch matcher; + vector result; + if ( + !NGEN::Regex::regexSearch( + __FILE__, + __LINE__, + log_line, + matcher, + isAlertErrorLog(log_line) ? alert_log_regex : error_log_regex + ) + ) { + dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format"; + return genError("Unexpected nginx log format"); + } + + const int event_message_index = 6; + const int source_index = 7; + const int request_index = 9; + const int host_index = 11; + string host = string(matcher[host_index].first, matcher[host_index].second); + string source = string(matcher[source_index].first, matcher[source_index].second); + string event_message = string(matcher[event_message_index].first, matcher[event_message_index].second); + string request = string(matcher[request_index].first, matcher[request_index].second); + + host = parseErrorLogField(host); + source = parseErrorLogField(source); + pair parsed_request = parseErrorLogRequestField(request); + string http_method = parsed_request.first; + string uri = parsed_request.second; + + if (NGEN::Regex::regexSearch(__FILE__, __LINE__, host, matcher, socket_address_regex)) { + int host_index = 1; + int port_index = 2; + host = string(matcher[host_index].first, matcher[host_index].second); + port = string(matcher[port_index].first, matcher[port_index].second); + } else if (NGEN::Regex::regexSearch(__FILE__, __LINE__, host, matcher, boost::regex("https://"))) { + port = "443"; + } else { + port = "80"; + } + + log_info[LogInfo::HOST] = host; + log_info[LogInfo::URI] = uri; + log_info[LogInfo::RESPONSE_CODE] = "500"; + log_info[LogInfo::HTTP_METHOD] = http_method; + log_info[LogInfo::SOURCE] = source; + log_info[LogInfo::DESTINATION_IP] = host; + log_info[LogInfo::DESTINATION_PORT] = port; + log_info[LogInfo::EVENT_MESSAGE] = event_message; + + addContextFieldsToLogInfo(log_info); + + if (!validateLog(log_info)) { + dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format"; + return genError("Unexpected nginx log format"); + } + + return log_info; + } + + Maybe> + parseAccessLog(const string &log_line) + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Parsing log line: " << log_line; + string formatted_log = log_line; + EnumArray log_info(EnumArray::Fill(), string("")); + vector result; + boost::erase_all(formatted_log, "\""); + boost::erase_all(formatted_log, "\n"); + boost::split(result, formatted_log, boost::is_any_of(" "), boost::token_compress_on); + + const int valid_log_size = 20; + + if (result.size() < valid_log_size) { + dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format"; + return genError("Unexpected nginx log format"); + } + + const int host_index = 6; + const int host_port_index = 7; + const int http_method_index = 13; + const int uri_index = 14; + const int response_cod_index = 16; + const int source_index = 8; + + log_info[LogInfo::HOST] = result[host_index]; + log_info[LogInfo::URI] = result[uri_index]; + log_info[LogInfo::RESPONSE_CODE] = result[response_cod_index]; + log_info[LogInfo::HTTP_METHOD] = result[http_method_index]; + log_info[LogInfo::SOURCE] = result[source_index]; + log_info[LogInfo::DESTINATION_IP] = result[host_index]; + log_info[LogInfo::DESTINATION_PORT] = result[host_port_index]; + log_info[LogInfo::EVENT_MESSAGE] = "Invalid request or incorrect reverse proxy configuration - " + "Request dropped. Please check the reverse proxy configuration of your relevant assets"; + + addContextFieldsToLogInfo(log_info); + + if (!validateLog(log_info)) { + dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format"; + return genError("Unexpected nginx log format"); + } + return log_info; + } + + static bool + validateLog(const EnumArray &log_info) + { + dbgFlow(D_NGINX_MESSAGE_READER); + + boost::smatch matcher; + if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, log_info[LogInfo::HOST], matcher, server_regex)) { + dbgTrace(D_NGINX_MESSAGE_READER) << "Could not validate server (Host): " << log_info[LogInfo::HOST]; + return false; + } + if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, log_info[LogInfo::URI], matcher, uri_regex)) { + dbgTrace(D_NGINX_MESSAGE_READER) << "Could not validate Uri: " << log_info[LogInfo::URI]; + return false; + } + + if ( + !NGEN::Regex::regexSearch( + __FILE__, + __LINE__, + log_info[LogInfo::RESPONSE_CODE], + matcher, response_code_regex + ) + ) { + dbgTrace(D_NGINX_MESSAGE_READER) + << "Could not validate response code: " + << log_info[LogInfo::RESPONSE_CODE]; + return false; + } + + if ( + !NGEN::Regex::regexSearch(__FILE__, __LINE__, log_info[LogInfo::HTTP_METHOD], matcher, http_method_regex) + ) { + dbgTrace(D_NGINX_MESSAGE_READER) << "Could not validate HTTP method: " << log_info[LogInfo::HTTP_METHOD]; + return false; + } + + if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, log_info[LogInfo::DESTINATION_PORT], matcher, port_regex)) { + dbgTrace(D_NGINX_MESSAGE_READER) + << "Could not validate destination port : " + << log_info[LogInfo::DESTINATION_PORT]; + return false; + } + + if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, log_info[LogInfo::SOURCE], matcher, server_regex)) { + dbgTrace(D_NGINX_MESSAGE_READER) << "Could not validate source : " << log_info[LogInfo::SOURCE]; + return false; + } + + return true; + } + + Maybe + getLogsFromSocket(const I_Socket::socketFd &client_socket) const + { + dbgFlow(D_NGINX_MESSAGE_READER) << "Reading logs from socket. fd: " << client_socket; + I_Socket *i_socket = Singleton::Consume::by(); + Maybe> raw_log_data = i_socket->receiveData(client_socket, 0, false); + if (!raw_log_data.ok()) { + dbgWarning(D_NGINX_MESSAGE_READER) << "Error receiving data from socket"; + return genError("Error receiving data from socket"); + } + + string raw_log(raw_log_data.unpack().begin(), raw_log_data.unpack().end()); + return move(raw_log); + } + + I_Socket::socketFd syslog_server_socket = -1; + string rate_limit_status_code = "429"; +}; + +NginxMessageReader::NginxMessageReader() : Component("NginxMessageReader"), pimpl(make_unique()) {} + +NginxMessageReader::~NginxMessageReader() {} + +void +NginxMessageReader::init() +{ + pimpl->init(); +} + +void +NginxMessageReader::preload() +{ + pimpl->preload(); +} + +void +NginxMessageReader::fini() +{ + pimpl->fini(); +} diff --git a/components/security_apps/central_nginx_manager/CMakeLists.txt b/components/security_apps/central_nginx_manager/CMakeLists.txt new file mode 100755 index 0000000..b58bb6b --- /dev/null +++ b/components/security_apps/central_nginx_manager/CMakeLists.txt @@ -0,0 +1,3 @@ +include_directories(include) + +add_library(central_nginx_manager central_nginx_manager.cc lets_encrypt_listener.cc) diff --git a/components/security_apps/central_nginx_manager/central_nginx_manager.cc b/components/security_apps/central_nginx_manager/central_nginx_manager.cc new file mode 100755 index 0000000..6848e95 --- /dev/null +++ b/components/security_apps/central_nginx_manager/central_nginx_manager.cc @@ -0,0 +1,390 @@ +// 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 "central_nginx_manager.h" +#include "lets_encrypt_listener.h" + +#include +#include +#include + +#include "debug.h" +#include "config.h" +#include "rest.h" +#include "log_generator.h" +#include "nginx_utils.h" +#include "agent_core_utilities.h" + +using namespace std; + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +class CentralNginxConfig +{ +public: + void load(cereal::JSONInputArchive &ar) + { + try { + string nginx_conf_base64; + ar(cereal::make_nvp("id", file_id)); + ar(cereal::make_nvp("name", file_name)); + ar(cereal::make_nvp("data", nginx_conf_base64)); + nginx_conf_content = cereal::base64::decode(nginx_conf_base64); + central_nginx_conf_path = getCentralNginxConfPath(); + shared_config_path = getSharedConfigPath(); + if (!nginx_conf_content.empty()) configureCentralNginx(); + } catch (const cereal::Exception &e) { + dbgDebug(D_NGINX_MANAGER) << "Could not load Central Management Config JSON. Error: " << e.what(); + ar.setNextName(nullptr); + } + } + + const string & getFileId() const { return file_id; } + const string & getFileName() const { return file_name; } + const string & getFileContent() const { return nginx_conf_content; } + + static string + getCentralNginxConfPath() + { + string central_nginx_conf_path = getProfileAgentSettingWithDefault( + string("/tmp/central_nginx.conf"), + "centralNginxManagement.confDownloadPath" + ); + dbgInfo(D_NGINX_MANAGER) << "Central NGINX configuration path: " << central_nginx_conf_path; + + return central_nginx_conf_path; + } + + static string + getSharedConfigPath() + { + string central_shared_conf_path = getConfigurationWithDefault( + "/etc/cp/conf", + "Config Component", + "configuration path" + ); + central_shared_conf_path += "/centralNginxManager/shared/central_nginx_shared.conf"; + dbgInfo(D_NGINX_MANAGER) << "Shared NGINX configuration path: " << central_shared_conf_path; + + return central_shared_conf_path; + } + +private: + void + loadAttachmentModule() + { + string attachment_module_path = NginxUtils::getModulesPath() + "/ngx_cp_attachment_module.so"; + if (!NGEN::Filesystem::exists(attachment_module_path)) { + dbgTrace(D_NGINX_MANAGER) << "Attachment module " << attachment_module_path << " does not exist"; + return; + } + + string attachment_module_conf = "load_module " + attachment_module_path + ";"; + if (nginx_conf_content.find(attachment_module_conf) != string::npos) { + dbgTrace(D_NGINX_MANAGER) << "Attachment module " << attachment_module_path << " already loaded"; + return; + } + + nginx_conf_content = attachment_module_conf + "\n" + nginx_conf_content; + } + + Maybe + loadSharedDirective(const string &directive) + { + dbgFlow(D_NGINX_MANAGER) << "Loading shared directive into the servers " << directive; + + if (!NGEN::Filesystem::copyFile(shared_config_path, shared_config_path + ".bak", true)) { + return genError("Could not create a backup of the shared NGINX configuration file"); + } + + ifstream shared_config(shared_config_path); + if (!shared_config.is_open()) { + return genError("Could not open shared NGINX configuration file"); + } + + string shared_config_content((istreambuf_iterator(shared_config)), istreambuf_iterator()); + shared_config.close(); + + if (shared_config_content.find(directive) != string::npos) { + dbgTrace(D_NGINX_MANAGER) << "Shared directive " << directive << " already loaded"; + return {}; + } + + ofstream new_shared_config(shared_config_path, ios::app); + if (!new_shared_config.is_open()) { + return genError("Could not open shared NGINX configuration file"); + } + + dbgTrace(D_NGINX_MANAGER) << "Adding shared directive " << directive; + new_shared_config << directive << "\n"; + new_shared_config.close(); + + auto validation = NginxUtils::validateNginxConf(central_nginx_conf_path); + if (!validation.ok()) { + if (!NGEN::Filesystem::copyFile(shared_config_path + ".bak", shared_config_path, true)) { + return genError("Could not restore the shared NGINX configuration file"); + } + return genError("Could not validate shared NGINX configuration file. Error: " + validation.getErr()); + } + + return {}; + } + + Maybe + loadSharedConfig() + { + dbgFlow(D_NGINX_MANAGER) << "Loading shared configuration into the servers"; + + ofstream shared_config(shared_config_path); + if (!shared_config.is_open()) { + return genError("Could not create shared NGINX configuration file"); + } + shared_config.close(); + + string shared_config_directive = "include " + shared_config_path + ";\n"; + boost::regex server_regex("server\\s*\\{"); + nginx_conf_content = NGEN::Regex::regexReplace( + __FILE__, + __LINE__, + nginx_conf_content, + server_regex, + "server {\n" + shared_config_directive + ); + + ofstream nginx_conf_file(central_nginx_conf_path); + if (!nginx_conf_file.is_open()) { + return genError("Could not open a temporary central NGINX configuration file"); + } + nginx_conf_file << nginx_conf_content; + nginx_conf_file.close(); + + auto validation = NginxUtils::validateNginxConf(central_nginx_conf_path); + if (!validation.ok()) { + return genError("Could not validate central NGINX configuration file. Error: " + validation.getErr()); + } + + return {}; + } + + Maybe + configureSyslog() + { + if (!getProfileAgentSettingWithDefault(true, "centralNginxManagement.syslogEnabled")) { + dbgTrace(D_NGINX_MANAGER) << "Syslog is disabled via settings"; + return {}; + } + + string syslog_directive = "error_log syslog:server=127.0.0.1:1514 warn;"; + auto load_shared_directive_result = loadSharedDirective(syslog_directive); + if (!load_shared_directive_result.ok()) { + return genError("Could not configure syslog directive, error: " + load_shared_directive_result.getErr()); + } + + return {}; + } + + Maybe + saveBaseCentralNginxConf() + { + ofstream central_nginx_conf_base_file(central_nginx_conf_path + ".base"); + if (!central_nginx_conf_base_file.is_open()) { + return genError("Could not open a temporary central NGINX configuration file"); + } + central_nginx_conf_base_file << nginx_conf_content; + central_nginx_conf_base_file.close(); + + return {}; + } + + void + configureCentralNginx() + { + loadAttachmentModule(); + auto save_base_nginx_conf = saveBaseCentralNginxConf(); + if (!save_base_nginx_conf.ok()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not save base NGINX configuration. Error: " + << save_base_nginx_conf.getErr(); + return; + } + + string nginx_conf_content_backup = nginx_conf_content; + auto shared_config_result = loadSharedConfig(); + if (!shared_config_result.ok()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not load shared configuration. Error: " + << shared_config_result.getErr(); + nginx_conf_content = nginx_conf_content_backup; + return; + } + + auto syslog_result = configureSyslog(); + if (!syslog_result.ok()) { + dbgWarning(D_NGINX_MANAGER) << "Could not configure syslog. Error: " << syslog_result.getErr(); + } + } + + string file_id; + string file_name; + string nginx_conf_content; + string central_nginx_conf_path; + string shared_config_path; +}; + +class CentralNginxManager::Impl +{ +public: + void + init() + { + dbgInfo(D_NGINX_MANAGER) << "Starting Central NGINX Manager"; + + string main_nginx_conf_path = NginxUtils::getMainNginxConfPath(); + if ( + NGEN::Filesystem::exists(main_nginx_conf_path) + && !NGEN::Filesystem::exists(main_nginx_conf_path + ".orig") + ) { + dbgInfo(D_NGINX_MANAGER) << "Creating a backup of the original main NGINX configuration file"; + NGEN::Filesystem::copyFile(main_nginx_conf_path, main_nginx_conf_path + ".orig", true); + } + + i_mainloop = Singleton::Consume::by(); + if (!lets_encrypt_listener.init()) { + dbgWarning(D_NGINX_MANAGER) << "Could not start Lets Encrypt Listener, scheduling retry"; + i_mainloop->addOneTimeRoutine( + I_MainLoop::RoutineType::System, + [this] () + { + while(!lets_encrypt_listener.init()) { + dbgWarning(D_NGINX_MANAGER) << "Could not start Lets Encrypt Listener, will retry"; + i_mainloop->yield(chrono::seconds(5)); + } + }, + "Lets Encrypt Listener initializer", + false + ); + } + } + + void + loadPolicy() + { + auto central_nginx_config = getSetting>("centralNginxManagement"); + if (!central_nginx_config.ok() || central_nginx_config.unpack().empty()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not load Central NGINX Management settings. Error: " + << central_nginx_config.getErr(); + return; + } + + auto &config = central_nginx_config.unpack().front(); + if (config.getFileContent().empty()) { + dbgWarning(D_NGINX_MANAGER) << "Empty NGINX configuration file"; + return; + } + + dbgTrace(D_NGINX_MANAGER) + << "Handling Central NGINX Management settings: " + << config.getFileId() + << ", " + << config.getFileName() + << ", " + << config.getFileContent(); + + string central_nginx_conf_path = config.getCentralNginxConfPath(); + ofstream central_nginx_conf_file(central_nginx_conf_path); + if (!central_nginx_conf_file.is_open()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not open central NGINX configuration file: " + << central_nginx_conf_path; + return; + } + central_nginx_conf_file << config.getFileContent(); + central_nginx_conf_file.close(); + + auto validation_result = NginxUtils::validateNginxConf(central_nginx_conf_path); + if (!validation_result.ok()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not validate central NGINX configuration file. Error: " + << validation_result.getErr(); + logError(validation_result.getErr()); + return; + } + + dbgTrace(D_NGINX_MANAGER) << "Validated central NGINX configuration file"; + + auto reload_result = NginxUtils::reloadNginx(central_nginx_conf_path); + if (!reload_result.ok()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not reload central NGINX configuration. Error: " + << reload_result.getErr(); + logError("Could not reload central NGINX configuration. Error: " + reload_result.getErr()); + return; + } + } + + void + fini() + { + string central_nginx_base_path = CentralNginxConfig::getCentralNginxConfPath() + ".base"; + if (!NGEN::Filesystem::exists(central_nginx_base_path)) { + dbgWarning(D_NGINX_MANAGER) << "Could not find base NGINX configuration file: " << central_nginx_base_path; + return; + } + + NginxUtils::reloadNginx(central_nginx_base_path); + } + +private: + void + logError(const string &error) + { + LogGen log( + error, + ReportIS::Audience::SECURITY, + ReportIS::Severity::CRITICAL, + ReportIS::Priority::HIGH, + ReportIS::Tags::POLICY_INSTALLATION + ); + } + + I_MainLoop *i_mainloop = nullptr; + LetsEncryptListener lets_encrypt_listener; +}; + +CentralNginxManager::CentralNginxManager() + : + Component("Central NGINX Manager"), + pimpl(make_unique()) {} + +CentralNginxManager::~CentralNginxManager() {} + +void +CentralNginxManager::init() +{ + pimpl->init(); +} + +void +CentralNginxManager::fini() +{ + pimpl->fini(); +} + +void +CentralNginxManager::preload() +{ + registerExpectedSetting>("centralNginxManagement"); + registerExpectedConfiguration("Config Component", "configuration path"); + registerConfigLoadCb([this]() { pimpl->loadPolicy(); }); +} diff --git a/components/security_apps/central_nginx_manager/include/lets_encrypt_listener.h b/components/security_apps/central_nginx_manager/include/lets_encrypt_listener.h new file mode 100755 index 0000000..cad66cf --- /dev/null +++ b/components/security_apps/central_nginx_manager/include/lets_encrypt_listener.h @@ -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. + +#ifndef __LETS_ENCRYPT_HANDLER_H__ +#define __LETS_ENCRYPT_HANDLER_H__ + +#include + +#include "maybe_res.h" + +class LetsEncryptListener +{ +public: + bool init(); + +private: + Maybe getChallengeValue(const std::string &uri) const; +}; + +#endif // __LETS_ENCRYPT_HANDLER_H__ diff --git a/components/security_apps/central_nginx_manager/lets_encrypt_listener.cc b/components/security_apps/central_nginx_manager/lets_encrypt_listener.cc new file mode 100755 index 0000000..93be82c --- /dev/null +++ b/components/security_apps/central_nginx_manager/lets_encrypt_listener.cc @@ -0,0 +1,76 @@ +// 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 "lets_encrypt_listener.h" + +#include + +#include "central_nginx_manager.h" +#include "debug.h" + +using namespace std; + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +bool +LetsEncryptListener::init() +{ + dbgInfo(D_NGINX_MANAGER) << "Starting Lets Encrypt Listener"; + return Singleton::Consume::by()->addWildcardGetCall( + ".well-known/acme-challenge/", + [&] (const string &uri) -> string + { + Maybe maybe_challenge_value = getChallengeValue(uri); + if (!maybe_challenge_value.ok()) { + dbgWarning(D_NGINX_MANAGER) + << "Could not get challenge value for uri: " + << uri + << ", error: " + << maybe_challenge_value.getErr(); + return string{""}; + }; + + dbgTrace(D_NGINX_MANAGER) << "Got challenge value: " << maybe_challenge_value.unpack(); + return maybe_challenge_value.unpack(); + } + ); +} + +Maybe +LetsEncryptListener::getChallengeValue(const string &uri) const +{ + string challenge_key = uri.substr(uri.find_last_of('/') + 1); + string api_query = "/api/lets-encrypt-challenge?http_challenge_key=" + challenge_key; + + dbgInfo(D_NGINX_MANAGER) << "Getting challenge value via: " << api_query; + + MessageMetadata md; + md.insertHeader("X-Tenant-Id", Singleton::Consume::by()->getTenantId()); + Maybe maybe_http_challenge_value = + Singleton::Consume::by()->sendSyncMessage( + HTTPMethod::GET, + api_query, + string("{}"), + MessageCategory::GENERIC, + md + ); + + if (!maybe_http_challenge_value.ok()) return genError(maybe_http_challenge_value.getErr().getBody()); + + string challenge_value = maybe_http_challenge_value.unpack().getBody(); + if (!challenge_value.empty() && challenge_value.front() == '"' && challenge_value.back() == '"') { + challenge_value = challenge_value.substr(1, challenge_value.size() - 2); + } + + return challenge_value; +} diff --git a/components/utils/nginx_utils/CMakeLists.txt b/components/utils/nginx_utils/CMakeLists.txt new file mode 100755 index 0000000..000525a --- /dev/null +++ b/components/utils/nginx_utils/CMakeLists.txt @@ -0,0 +1 @@ +add_library(nginx_utils nginx_utils.cc) diff --git a/components/utils/nginx_utils/nginx_utils.cc b/components/utils/nginx_utils/nginx_utils.cc new file mode 100755 index 0000000..e0af8fa --- /dev/null +++ b/components/utils/nginx_utils/nginx_utils.cc @@ -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. + +#include "nginx_utils.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "maybe_res.h" +#include "config.h" +#include "agent_core_utilities.h" + +using namespace std; + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +NginxConfCollector::NginxConfCollector(const string &input_path, const string &output_path) + : + main_conf_input_path(input_path), + main_conf_output_path(output_path) +{ + main_conf_directory_path = main_conf_input_path.substr(0, main_conf_input_path.find_last_of('/')); +} + +vector +NginxConfCollector::expandIncludes(const string &include_pattern) const { + vector matching_files; + string absolute_include_pattern = include_pattern; + string maybe_directory = include_pattern.substr(0, include_pattern.find_last_of('/')); + if (!maybe_directory.empty() && maybe_directory.front() != '/') { + dbgTrace(D_NGINX_MANAGER) << "Include pattern is a relative path: " << include_pattern; + maybe_directory = main_conf_directory_path + '/' + maybe_directory; + absolute_include_pattern = main_conf_directory_path + '/' + include_pattern; + } + + if (!NGEN::Filesystem::exists(maybe_directory)) { + dbgTrace(D_NGINX_MANAGER) << "Include pattern directory/file does not exist: " << maybe_directory; + return matching_files; + } + + string filename_pattern = absolute_include_pattern.substr(absolute_include_pattern.find_last_of('/') + 1); + boost::regex wildcard_regex("\\*"); + boost::regex pattern( + NGEN::Regex::regexReplace(__FILE__, __LINE__, filename_pattern, wildcard_regex, string("[^/]*")) + ); + + if (!NGEN::Filesystem::isDirectory(maybe_directory)) { + dbgTrace(D_NGINX_MANAGER) << "Include pattern is a file: " << absolute_include_pattern; + matching_files.push_back(absolute_include_pattern); + return matching_files; + } + + DIR* dir = opendir(maybe_directory.c_str()); + if (!dir) { + dbgTrace(D_NGINX_MANAGER) << "Could not open directory: " << maybe_directory; + return matching_files; + } + + struct dirent *entry; + while ((entry = readdir(dir)) != nullptr) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + + if (NGEN::Regex::regexMatch(__FILE__, __LINE__, entry->d_name, pattern)) { + matching_files.push_back(maybe_directory + "/" + entry->d_name); + dbgTrace(D_NGINX_MANAGER) << "Matched file: " << maybe_directory << '/' << entry->d_name; + } + } + closedir(dir); + + return matching_files; +} + +void +NginxConfCollector::processConfigFile(const string &path, ostringstream &conf_output, vector &errors) const +{ + ifstream file(path); + if (!file.is_open()) return; + + string content((istreambuf_iterator(file)), istreambuf_iterator()); + file.close(); + + dbgTrace(D_NGINX_MANAGER) << "Processing file: " << path; + + if (content.empty()) return; + + try { + boost::regex include_regex(R"(^\s*include\s+([^;]+);)"); + boost::smatch match; + + while (NGEN::Regex::regexSearch(__FILE__, __LINE__, content, match, include_regex)) { + string include_pattern = match[1].str(); + include_pattern = NGEN::Strings::trim(include_pattern); + dbgTrace(D_NGINX_MANAGER) << "Include pattern: " << include_pattern; + + vector included_files = expandIncludes(include_pattern); + if (included_files.empty()) { + dbgTrace(D_NGINX_MANAGER) << "No files matched the include pattern: " << include_pattern; + content.replace(match.position(), match.length(), ""); + continue; + } + + ostringstream included_content; + for (const string &included_file : included_files) { + dbgTrace(D_NGINX_MANAGER) << "Processing included file: " << included_file; + processConfigFile(included_file, included_content, errors); + } + content.replace(match.position(), match.length(), included_content.str()); + } + } catch (const boost::regex_error &e) { + errors.emplace_back(e.what()); + return; + } catch (const exception &e) { + errors.emplace_back(e.what()); + return; + } + + conf_output << content; +} + +Maybe +NginxConfCollector::generateFullNginxConf() const +{ + if (!NGEN::Filesystem::exists(main_conf_input_path)) { + return genError("Input file does not exist: " + main_conf_input_path); + } + + ostringstream conf_output; + vector errors; + processConfigFile(main_conf_input_path, conf_output, errors); + + if (!errors.empty()) { + for (const string &error : errors) dbgWarning(D_NGINX_MANAGER) << error; + return genError("Errors occurred while processing configuration files"); + } + + ofstream single_nginx_conf_file(main_conf_output_path); + if (!single_nginx_conf_file.is_open()) return genError("Could not create output file: " + main_conf_output_path); + + single_nginx_conf_file << conf_output.str(); + single_nginx_conf_file.close(); + + return NGEN::Filesystem::resolveFullPath(main_conf_output_path); +} + +string +NginxUtils::getMainNginxConfPath() +{ + static string main_nginx_conf_path; + if (!main_nginx_conf_path.empty()) return main_nginx_conf_path; + + auto main_nginx_conf_path_setting = getProfileAgentSetting("centralNginxManagement.mainConfPath"); + if (main_nginx_conf_path_setting.ok()) { + main_nginx_conf_path = main_nginx_conf_path_setting.unpack(); + return main_nginx_conf_path; + } + + string default_main_nginx_conf_path = "/etc/nginx/nginx.conf"; + string command = "nginx -V 2>&1"; + auto result = Singleton::Consume::by()->getExecOutputAndCode(command); + if (!result.ok()) return default_main_nginx_conf_path; + + string output = result.unpack().first; + boost::regex conf_regex(R"(--conf-path=([^ ]+))"); + boost::smatch match; + if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, output, match, conf_regex)) { + main_nginx_conf_path = default_main_nginx_conf_path; + return main_nginx_conf_path; + } + + string conf_path = match[1].str(); + conf_path = NGEN::Strings::trim(conf_path); + if (conf_path.empty()) { + main_nginx_conf_path = default_main_nginx_conf_path; + return main_nginx_conf_path; + } + + main_nginx_conf_path = conf_path; + return main_nginx_conf_path; +} + +string +NginxUtils::getModulesPath() +{ + static string main_modules_path; + if (!main_modules_path.empty()) return main_modules_path; + + auto modules_path_setting = getProfileAgentSetting("centralNginxManagement.modulesPath"); + if (modules_path_setting.ok()) { + main_modules_path = modules_path_setting.unpack(); + return main_modules_path; + } + + string default_modules_path = "/usr/share/nginx/modules"; + string command = "nginx -V 2>&1"; + auto result = Singleton::Consume::by()->getExecOutputAndCode(command); + if (!result.ok()) return default_modules_path; + + string output = result.unpack().first; + boost::regex modules_regex(R"(--modules-path=([^ ]+))"); + boost::smatch match; + if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, output, match, modules_regex)) { + main_modules_path = default_modules_path; + return main_modules_path; + } + + string modules_path = match[1].str(); + modules_path = NGEN::Strings::trim(modules_path); + if (modules_path.empty()) { + main_modules_path = default_modules_path; + return main_modules_path; + } + + main_modules_path = modules_path; + return modules_path; +} + +Maybe +NginxUtils::validateNginxConf(const string &nginx_conf_path) +{ + dbgTrace(D_NGINX_MANAGER) << "Validating NGINX configuration file: " << nginx_conf_path; + if (!NGEN::Filesystem::exists(nginx_conf_path)) return genError("Nginx configuration file does not exist"); + + string command = "nginx -t -c " + nginx_conf_path + " 2>&1"; + auto result = Singleton::Consume::by()->getExecOutputAndCode(command); + if (!result.ok()) return genError(result.getErr()); + if (result.unpack().second != 0) return genError(result.unpack().first); + + dbgTrace(D_NGINX_MANAGER) << "NGINX configuration file is valid"; + + return {}; +} + +Maybe +NginxUtils::reloadNginx(const string &nginx_conf_path) +{ + dbgTrace(D_NGINX_MANAGER) << "Applying and reloading new NGINX configuration file: " << nginx_conf_path; + string main_nginx_conf_path = getMainNginxConfPath(); + + string backup_conf_path = main_nginx_conf_path + ".bak"; + if ( + NGEN::Filesystem::exists(main_nginx_conf_path) + && !NGEN::Filesystem::copyFile(main_nginx_conf_path, backup_conf_path, true) + ) { + return genError("Could not create backup of NGINX configuration file"); + } + + dbgTrace(D_NGINX_MANAGER) << "Copying new NGINX configuration file to: " << main_nginx_conf_path; + if (!NGEN::Filesystem::copyFile(nginx_conf_path, main_nginx_conf_path, true)) { + return genError("Could not copy new NGINX configuration file"); + } + + string command = "nginx -s reload 2>&1"; + auto result = Singleton::Consume::by()->getExecOutputAndCode(command); + if (!result.ok() || result.unpack().second != 0) { + if (!NGEN::Filesystem::copyFile(backup_conf_path, main_nginx_conf_path, true)) { + return genError("Could not restore backup of NGINX configuration file"); + } + dbgTrace(D_NGINX_MANAGER) << "Successfully restored backup of NGINX configuration file"; + return result.ok() ? genError(result.unpack().first) : genError(result.getErr()); + } + + dbgInfo(D_NGINX_MANAGER) << "Successfully reloaded NGINX configuration file"; + + return {}; +} diff --git a/components/utils/utilities/CMakeLists.txt b/components/utils/utilities/CMakeLists.txt new file mode 100755 index 0000000..f4655dd --- /dev/null +++ b/components/utils/utilities/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(nginx_conf_collector) \ No newline at end of file diff --git a/components/utils/utilities/nginx_conf_collector/CMakeLists.txt b/components/utils/utilities/nginx_conf_collector/CMakeLists.txt new file mode 100755 index 0000000..c18fc1e --- /dev/null +++ b/components/utils/utilities/nginx_conf_collector/CMakeLists.txt @@ -0,0 +1,63 @@ +include_directories(${PROJECT_SOURCE_DIR}/core/include/) + +link_directories(${Boost_LIBRARY_DIRS}) +link_directories(${ZLIB_ROOT}/lib) + +link_directories(${ZLIB_ROOT}/lib) +link_directories(${CMAKE_BINARY_DIR}/core) +link_directories(${CMAKE_BINARY_DIR}/core/compression) + +SET(EXECUTABLE_NAME "nginx_conf_collector_bin") +add_executable(${EXECUTABLE_NAME} nginx_conf_collector.cc) +target_compile_definitions(${EXECUTABLE_NAME} PRIVATE "NGINX_CONF_COLLECTOR_VERSION=\"$ENV{CI_PIPELINE_ID}\"") + +# if("${PLATFORM_TYPE}" STREQUAL "x86") +set_target_properties(${EXECUTABLE_NAME} PROPERTIES + LINK_FLAGS "-static -static-libgcc -static-libstdc++" +) +target_link_libraries(${EXECUTABLE_NAME} + shell_cmd + mainloop + messaging + event_is + metric + static_compression_utils + z + nginx_utils + time_proxy + debug_is + version + report + config + environment + singleton + rest + boost_context + boost_regex + pthread +) +# endif() + + +# target_link_libraries(${EXECUTABLE_NAME} +# shell_cmd +# mainloop +# messaging +# event_is +# metric +# compression_utils +# -lz +# nginx_utils +# time_proxy +# debug_is +# version +# report +# config +# environment +# singleton +# rest +# boost_context +# ) + +install(TARGETS ${EXECUTABLE_NAME} DESTINATION bin) +install(PROGRAMS ${EXECUTABLE_NAME} DESTINATION central_nginx_manager/bin RENAME cp-nano-nginx-conf-collector) diff --git a/components/utils/utilities/nginx_conf_collector/nginx_conf_collector.cc b/components/utils/utilities/nginx_conf_collector/nginx_conf_collector.cc new file mode 100755 index 0000000..092e15f --- /dev/null +++ b/components/utils/utilities/nginx_conf_collector/nginx_conf_collector.cc @@ -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 +#include + +#include "agent_core_utilities.h" +#include "debug.h" +#include "internal/shell_cmd.h" +#include "mainloop.h" +#include "nginx_utils.h" +#include "time_proxy.h" + +using namespace std; + +USE_DEBUG_FLAG(D_NGINX_MANAGER); + +class MainComponent +{ +public: + MainComponent() + { + time_proxy.init(); + environment.init(); + mainloop.init(); + shell_cmd.init(); + } + + ~MainComponent() + { + shell_cmd.fini(); + mainloop.fini(); + environment.fini(); + time_proxy.fini(); + } +private: + ShellCmd shell_cmd; + MainloopComponent mainloop; + Environment environment; + TimeProxyComponent time_proxy; +}; + +void +printVersion() +{ +#ifdef NGINX_CONF_COLLECTOR_VERSION + cout << "Check Point NGINX configuration collector version: " << NGINX_CONF_COLLECTOR_VERSION << '\n'; +#else + cout << "Check Point NGINX configuration collector version: Private" << '\n'; +#endif +} + +void +printUsage(const char *prog_name) +{ + cout << "Usage: " << prog_name << " [-v] [-i /path/to/nginx.conf] [-o /path/to/output.conf]" << '\n'; + cout << " -V Print version" << '\n'; + cout << " -v Enable verbose output" << '\n'; + cout << " -i input_file Specify input file (default is /etc/nginx/nginx.conf)" << '\n'; + cout << " -o output_file Specify output file (default is ./full_nginx.conf)" << '\n'; + cout << " -h Print this help message" << '\n'; +} + +int +main(int argc, char *argv[]) +{ + string nginx_input_file = "/etc/nginx/nginx.conf"; + string nginx_output_file = "full_nginx.conf"; + + int opt; + while ((opt = getopt(argc, argv, "Vvhi:o:h")) != -1) { + switch (opt) { + case 'V': + printVersion(); + return 0; + case 'v': + Debug::setUnitTestFlag(D_NGINX_MANAGER, Debug::DebugLevel::TRACE); + break; + case 'i': + nginx_input_file = optarg; + break; + case 'o': + nginx_output_file = optarg; + break; + case 'h': + printUsage(argv[0]); + return 0; + default: + printUsage(argv[0]); + return 1; + } + } + + for (int i = optind; i < argc;) { + cerr << "Unknown argument: " << argv[i] << '\n'; + printUsage(argv[0]); + return 1; + } + + dbgTrace(D_NGINX_MANAGER) << "Starting nginx configuration collector"; + + MainComponent main_component; + auto validation_result = NginxUtils::validateNginxConf(nginx_input_file); + if (!validation_result.ok()) { + cerr + << "Could not validate nginx configuration file: " + << nginx_input_file + << '\n' + << validation_result.getErr(); + return 1; + } + + NginxConfCollector nginx_collector(nginx_input_file, nginx_output_file); + auto result = nginx_collector.generateFullNginxConf(); + if (!result.ok()) { + cerr << "Could not generate full nginx configuration file, error: " << result.getErr() << '\n'; + return 1; + } + + if (result.unpack().empty() || !NGEN::Filesystem::exists(result.unpack())) { + cerr << "Generated nginx configuration file does not exist: " << result.unpack() << '\n'; + return 1; + } + + validation_result = NginxUtils::validateNginxConf(result.unpack()); + if (!validation_result.ok()) { + cerr + << "Could not validate generated nginx configuration file: " + << nginx_output_file + << '\n' + << validation_result.getErr(); + return 1; + } + + cout << "Full nginx configuration file was successfully generated: " << result.unpack() << '\n'; + + return 0; +} diff --git a/nodes/central_nginx_manager/CMakeLists.txt b/nodes/central_nginx_manager/CMakeLists.txt new file mode 100755 index 0000000..783e929 --- /dev/null +++ b/nodes/central_nginx_manager/CMakeLists.txt @@ -0,0 +1,34 @@ +add_subdirectory(package) + +add_executable(cp-nano-central-nginx-manager main.cc) + +target_link_libraries(cp-nano-central-nginx-manager + -Wl,--start-group + ${COMMON_LIBRARIES} + + generic_rulebase + generic_rulebase_evaluators + ip_utilities + version + signal_handler + + central_nginx_manager + nginx_message_reader + rate_limit_comp + rate_limit_config + nginx_utils + http_transaction_data + -Wl,--end-group +) + +add_dependencies(cp-nano-central-nginx-manager ngen_core) + +install(TARGETS cp-nano-central-nginx-manager DESTINATION bin) +install(TARGETS cp-nano-central-nginx-manager DESTINATION central_nginx_manager/bin) + +gen_package( + install-cp-nano-central-nginx-manager.sh + central_nginx_manager + ./install-cp-nano-central-nginx-manager.sh + Check Point Central NGINX Manager Nano Service Version ${PACKAGE_VERSION} Install Package +) \ No newline at end of file diff --git a/nodes/central_nginx_manager/main.cc b/nodes/central_nginx_manager/main.cc new file mode 100755 index 0000000..bad8e39 --- /dev/null +++ b/nodes/central_nginx_manager/main.cc @@ -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. + +#include "central_nginx_manager.h" + +#include "components_list.h" +#include "nginx_message_reader.h" + +using namespace std; + +int +main(int argc, char **argv) +{ + NodeComponents comps; + + comps.registerGlobalValue("Is Rest primary routine", true); + comps.registerGlobalValue("Nano service API Port Primary", 7555); + comps.registerGlobalValue("Nano service API Port Alternative", 7556); + + return comps.run("Central NGINX Manager", argc, argv); +} diff --git a/nodes/central_nginx_manager/package/CMakeLists.txt b/nodes/central_nginx_manager/package/CMakeLists.txt new file mode 100755 index 0000000..fe82417 --- /dev/null +++ b/nodes/central_nginx_manager/package/CMakeLists.txt @@ -0,0 +1,4 @@ +install(FILES install-cp-nano-central-nginx-manager.sh DESTINATION central_nginx_manager PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) +install(FILES cp-nano-central-nginx-manager.cfg DESTINATION central_nginx_manager/conf PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) +install(FILES cp-nano-central-nginx-manager-conf.json DESTINATION central_nginx_manager/conf PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) +install(FILES cp-nano-central-nginx-manager-debug-conf.json DESTINATION central_nginx_manager/conf PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ) diff --git a/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager-conf.json b/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager-conf.json new file mode 100755 index 0000000..9e26dfe --- /dev/null +++ b/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager-conf.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager-debug-conf.json b/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager-debug-conf.json new file mode 100755 index 0000000..7b9773c --- /dev/null +++ b/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager-debug-conf.json @@ -0,0 +1,11 @@ +{ + "Debug": [ + { + "Streams": [ + { + "Output": "/var/log/nano_agent/cp-nano-central-nginx-manager.dbg" + } + ] + } + ] +} \ No newline at end of file diff --git a/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager.cfg b/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager.cfg new file mode 100755 index 0000000..a4a5210 --- /dev/null +++ b/nodes/central_nginx_manager/package/cp-nano-central-nginx-manager.cfg @@ -0,0 +1,2 @@ +srv_debug_file=/var/log/nano_agent/cp-nano-central-nginx-manager.dbg +srv_log_file=/var/log/nano_agent/cp-nano-central-nginx-manager.log \ No newline at end of file diff --git a/nodes/central_nginx_manager/package/install-cp-nano-central-nginx-manager.sh b/nodes/central_nginx_manager/package/install-cp-nano-central-nginx-manager.sh new file mode 100755 index 0000000..5c00f04 --- /dev/null +++ b/nodes/central_nginx_manager/package/install-cp-nano-central-nginx-manager.sh @@ -0,0 +1,171 @@ +#!/bin/sh + +FORCE_STDOUT=true +INSTALLATION_LOG_FILE="/var/log/nano_agent/install-cp-nano-central-nginx-manager.log" +INSTALLATION_TIME=$(date) +CONF_PATH=/etc/cp/conf +SERVICE_PATH=/etc/cp/centralNginxManager +WATCHDOG_PATH=/etc/cp/watchdog/cp-nano-watchdog +NGINX_CONF_PATH="/etc/nginx/nginx.conf" +CENTRAL_NGINX_CONF_PATH="/tmp/central_nginx.conf" + +export INSTALL_COMMAND +is_install="$(command -v install)" +if [ -z ${is_install} ]; then + INSTALL_COMMAND="cp -f" + cp_print "[WARNING]: install command not found - using cp instead" ${FORCE_STDOUT} +else + INSTALL_COMMAND=install +fi + +mkdir -p /var/log/nano_agent +mkdir -p /tmp/ + +cp_print() +{ + var_text=${1} + var_std_out=${2} + touch ${INSTALLATION_LOG_FILE} + if [ -n "${var_std_out}" ]; then + if [ "${var_std_out}" = "true" ]; then + printf "%b\n" "${var_text}" + fi + fi + printf "%b\n" "${var_text}" >> ${INSTALLATION_LOG_FILE} +} + +cp_exec() +{ + var_cmd=${1} + var_std_out=${2} + # Send exec output to RES + RES=$(${var_cmd} 2>&1) + if [ -n "${RES}" ]; then + cp_print "${RES}" "${var_std_out}" + fi +} + +is_nginx_installed() +{ + if [ -x "$(command -v nginx)" ]; then + return 0 + fi + + return 1 +} + +get_nginx_conf_path() +{ + if ! is_nginx_installed; then + return + fi + + NGINX_CONF_PATH=$(nginx -V 2>&1 | grep -o '\--conf-path=[^ ]*' | cut -d= -f2) + if [ -z "${NGINX_CONF_PATH}" ]; then + NGINX_CONF_PATH="/etc/nginx/nginx.conf" + fi +} + +run_installation() +{ + cp_print "Starting installation of Check Point Central NGINX Manager [${INSTALLATION_TIME}]\n" ${FORCE_STDOUT} + cp_exec "${WATCHDOG_PATH} --un-register ${SERVICE_PATH}/cp-nano-central-nginx-manager" + cp_exec "mkdir -p ${SERVICE_PATH}" + cp_exec "mkdir -p ${CONF_PATH}/centralNginxManager/shared" + + cp_exec "touch ${CONF_PATH}/centralNginxManager/shared/central_nginx_shared.conf" + cp_exec "${INSTALL_COMMAND} bin/cp-nano-central-nginx-manager ${SERVICE_PATH}/cp-nano-central-nginx-manager" + cp_exec "${INSTALL_COMMAND} bin/cp-nano-nginx-conf-collector ${SERVICE_PATH}/cp-nano-nginx-conf-collector" + cp_exec "${INSTALL_COMMAND} conf/cp-nano-central-nginx-manager.cfg ${CONF_PATH}/cp-nano-central-nginx-manager.cfg" + cp_exec "${INSTALL_COMMAND} conf/cp-nano-central-nginx-manager-conf.json ${CONF_PATH}/cp-nano-central-nginx-manager-conf.json" + cp_exec "${INSTALL_COMMAND} conf/cp-nano-central-nginx-manager-debug-conf.json ${CONF_PATH}/cp-nano-central-nginx-manager-debug-conf.json" + cp_exec "chmod +x ${SERVICE_PATH}/cp-nano-central-nginx-manager" + cp_exec "chmod +x ${SERVICE_PATH}/cp-nano-nginx-conf-collector" + cp_exec "chmod 600 ${CONF_PATH}/cp-nano-central-nginx-manager.cfg" + cp_exec "chmod 600 ${CONF_PATH}/cp-nano-central-nginx-manager-conf.json" + + cp_exec "${WATCHDOG_PATH} --register ${SERVICE_PATH}/cp-nano-central-nginx-manager" + cp_print "Installation completed successfully." ${FORCE_STDOUT} +} + +usage() +{ + echo "Check Point: available flags are" + echo "--install : install central nginx manager" + echo "--uninstall : remove central nginx manager" + echo "--pre_install_test : run Pre-installation test for central nginx manager install package" + echo "--post_install_test : run Post-installation test for central nginx manager install package" + exit 255 +} + +run_uninstall() +{ + cp_print "Starting uninstall of Check Point Central NGINX Manager service [${INSTALLATION_TIME}]\n" ${FORCE_STDOUT} + + cp_exec "${WATCHDOG_PATH} --un-register ${SERVICE_PATH}/cp-nano-central-nginx-manager" + cp_exec "rm -rf ${SERVICE_PATH}/" + cp_exec "rm -f ${CONF_PATH}/cp-nano-central-nginx-manager.cfg" + cp_exec "rm -f ${CONF_PATH}/cp-nano-central-nginx-manager-conf.json" + + if [ -f "${CENTRAL_NGINX_CONF_PATH}.base" ]; then + cp_print "Restoring central NGINX configuration file" ${FORCE_STDOUT} + cp_exec "${INSTALL_COMMAND} ${CENTRAL_NGINX_CONF_PATH}.base ${NGINX_CONF_PATH}" + if is_nginx_installed; then + if nginx -t > /dev/null 2>&1; then + cp_exec "nginx -s reload" + else + cp_print "Could not reload central NGINX configuration, run 'nginx -t' for more details." ${FORCE_STDOUT} + fi + fi + fi + + if [ -f "${NGINX_CONF_PATH}.orig" ]; then + cp_print "Original (pre Check Point Nano Agent deployment) NGINX configuration file can be found at: ${NGINX_CONF_PATH}.orig" ${FORCE_STDOUT} + fi + cp_print "Check Point Central NGINX Manager service was removed successfully\n" ${FORCE_STDOUT} +} + +run_pre_install_test() +{ + cp_print "Successfully finished pre-installation test for Check Point Central NGINX Manager service installation package [${INSTALLATION_TIME}]\n" ${FORCE_STDOUT} + exit 0 +} + +run_post_install_test() +{ + if [ ! -d ${SERVICE_PATH} ]; then + cp_print "Failed post-installation test for Check Point Central NGINX Manager service installation package [${INSTALLATION_TIME}]\n" ${FORCE_STDOUT} + exit 1 + fi + + cp_print "Successfully finished post-installation test for Check Point Central NGINX Manager service installation package [${INSTALLATION_TIME}]\n" ${FORCE_STDOUT} + exit 0 +} + + +run() +{ + get_nginx_conf_path + if [ '--install' = "${1}" ]; then + run_installation "${@}" + elif [ '--uninstall' = "${1}" ]; then + run_uninstall + elif [ '--pre_install_test' = "${1}" ]; then + run_pre_install_test + elif [ '--post_install_test' = "${1}" ]; then + run_post_install_test + else + usage + exit 1 + fi +} + +if [ "$(id -u)" != "0" ]; then + echo "Administrative privileges required for this Package (use su or sudo)" + exit 1 +fi + +shift +run "${@}" + +exit 0