mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-29 19:24:26 +03:00
central nginx manager
This commit is contained in:
3
components/security_apps/central_nginx_manager/CMakeLists.txt
Executable file
3
components/security_apps/central_nginx_manager/CMakeLists.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
include_directories(include)
|
||||
|
||||
add_library(central_nginx_manager central_nginx_manager.cc lets_encrypt_listener.cc)
|
390
components/security_apps/central_nginx_manager/central_nginx_manager.cc
Executable file
390
components/security_apps/central_nginx_manager/central_nginx_manager.cc
Executable file
@@ -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 <string>
|
||||
#include <vector>
|
||||
#include <cereal/external/base64.hpp>
|
||||
|
||||
#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>(
|
||||
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<string>(
|
||||
"/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<void>
|
||||
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<char>(shared_config)), istreambuf_iterator<char>());
|
||||
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<void>
|
||||
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<void>
|
||||
configureSyslog()
|
||||
{
|
||||
if (!getProfileAgentSettingWithDefault<bool>(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<void>
|
||||
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<I_MainLoop>::by<CentralNginxManager>();
|
||||
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<vector<CentralNginxConfig>>("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::Impl>()) {}
|
||||
|
||||
CentralNginxManager::~CentralNginxManager() {}
|
||||
|
||||
void
|
||||
CentralNginxManager::init()
|
||||
{
|
||||
pimpl->init();
|
||||
}
|
||||
|
||||
void
|
||||
CentralNginxManager::fini()
|
||||
{
|
||||
pimpl->fini();
|
||||
}
|
||||
|
||||
void
|
||||
CentralNginxManager::preload()
|
||||
{
|
||||
registerExpectedSetting<vector<CentralNginxConfig>>("centralNginxManagement");
|
||||
registerExpectedConfiguration<string>("Config Component", "configuration path");
|
||||
registerConfigLoadCb([this]() { pimpl->loadPolicy(); });
|
||||
}
|
@@ -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 <string>
|
||||
|
||||
#include "maybe_res.h"
|
||||
|
||||
class LetsEncryptListener
|
||||
{
|
||||
public:
|
||||
bool init();
|
||||
|
||||
private:
|
||||
Maybe<std::string> getChallengeValue(const std::string &uri) const;
|
||||
};
|
||||
|
||||
#endif // __LETS_ENCRYPT_HANDLER_H__
|
76
components/security_apps/central_nginx_manager/lets_encrypt_listener.cc
Executable file
76
components/security_apps/central_nginx_manager/lets_encrypt_listener.cc
Executable file
@@ -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 <string>
|
||||
|
||||
#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<I_RestApi>::by<CentralNginxManager>()->addWildcardGetCall(
|
||||
".well-known/acme-challenge/",
|
||||
[&] (const string &uri) -> string
|
||||
{
|
||||
Maybe<string> 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<string>
|
||||
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<I_AgentDetails>::by<CentralNginxManager>()->getTenantId());
|
||||
Maybe<HTTPResponse, HTTPResponse> maybe_http_challenge_value =
|
||||
Singleton::Consume<I_Messaging>::by<CentralNginxManager>()->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;
|
||||
}
|
Reference in New Issue
Block a user