refactor: drop civetweb dependency and implement healtz using cpp-httplib

Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
This commit is contained in:
Jason Dellaluce
2022-03-23 13:37:30 +00:00
committed by poiana
parent 42fcc7291f
commit b91ff34b97
10 changed files with 145 additions and 413 deletions

View File

@@ -100,7 +100,7 @@ if(NOT MINIMAL_BUILD)
list(
APPEND FALCO_INCLUDE_DIRECTORIES
"${CIVETWEB_INCLUDE_DIR}"
"${CPPHTTPLIB_INCLUDE}"
"${OPENSSL_INCLUDE_DIR}"
"${GRPC_INCLUDE}"
"${GRPCPP_INCLUDE}"
@@ -113,7 +113,7 @@ if(NOT MINIMAL_BUILD)
list(APPEND FALCO_LIBRARIES "${GRPC_LIBRARIES}")
endif()
list(APPEND FALCO_DEPENDENCIES civetweb)
list(APPEND FALCO_DEPENDENCIES cpp-httplib)
list(
APPEND FALCO_LIBRARIES
@@ -124,8 +124,6 @@ if(NOT MINIMAL_BUILD)
"${CARES_LIB}"
"${OPENSSL_LIBRARIES}"
"${YAMLCPP_LIB}"
"${CIVETWEB_LIB}"
"${CIVETWEB_CPP_LIB}"
)
endif()

View File

@@ -30,7 +30,11 @@ application::run_result application::start_webserver()
{
std::string ssl_option = (m_state->config->m_webserver_ssl_enabled ? " (SSL)" : "");
falco_logger::log(LOG_INFO, "Starting internal webserver, listening on port " + to_string(m_state->config->m_webserver_listen_port) + ssl_option + "\n");
m_state->webserver.start();
m_state->webserver.start(
m_state->config->m_webserver_listen_port,
m_state->config->m_webserver_k8s_healthz_endpoint,
m_state->config->m_webserver_ssl_certificate,
m_state->config->m_webserver_ssl_enabled);
}
return ret;

View File

@@ -64,7 +64,6 @@ int falco_init(int argc, char **argv, bool &restart)
if(!success)
{
result = EXIT_FAILURE;
fprintf(stderr, "%s\n", errstr.c_str());
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2019 The Falco Authors.
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,258 +14,100 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdio.h>
#include <string.h>
#include "falco_common.h"
#include "webserver.h"
#include "json_evt.h"
#include "banned.h" // This raises a compilation error when certain functions are used
using json = nlohmann::json;
using namespace std;
k8s_audit_handler::k8s_audit_handler(std::shared_ptr<falco_engine> engine, std::shared_ptr<falco_outputs> outputs, std::size_t k8s_audit_event_source_idx):
m_engine(engine), m_outputs(outputs), m_k8s_audit_event_source_idx(k8s_audit_event_source_idx)
{
}
k8s_audit_handler::~k8s_audit_handler()
{
}
bool k8s_healthz_handler::handleGet(CivetServer *server, struct mg_connection *conn)
{
const std::string status_body = "{\"status\": \"ok\"}";
mg_send_http_ok(conn, "application/json", status_body.size());
mg_printf(conn, "%s", status_body.c_str());
return true;
}
bool k8s_audit_handler::accept_data(std::shared_ptr<falco_engine> engine,
std::shared_ptr<falco_outputs> outputs,
std::size_t k8s_audit_event_source_idx,
std::string &data,
std::string &errstr)
{
std::list<json_event> jevts;
json j;
try
{
j = json::parse(data);
}
catch(json::parse_error &e)
{
errstr = string("Could not parse data: ") + e.what();
return false;
}
catch(json::out_of_range &e)
{
errstr = string("Could not parse data: ") + e.what();
return false;
}
bool ok;
try
{
ok = falco_k8s_audit::parse_k8s_audit_json(j, jevts);
}
catch(json::type_error &e)
{
ok = false;
}
if(!ok)
{
errstr = string("Data not recognized as a k8s audit event");
return false;
}
for(auto &jev : jevts)
{
std::unique_ptr<falco_engine::rule_result> res;
try
{
res = engine->process_event(k8s_audit_event_source_idx, &jev);
}
catch(...)
{
errstr = string("unknown error processing audit event");
fprintf(stderr, "%s\n", errstr.c_str());
return false;
}
if(res)
{
try
{
outputs->handle_event(res->evt, res->rule,
res->source, res->priority_num,
res->format, res->tags);
}
catch(falco_exception &e)
{
errstr = string("Internal error handling output: ") + e.what();
fprintf(stderr, "%s\n", errstr.c_str());
return false;
}
}
}
return true;
}
bool k8s_audit_handler::accept_uploaded_data(std::string &post_data, std::string &errstr)
{
return k8s_audit_handler::accept_data(m_engine, m_outputs, m_k8s_audit_event_source_idx, post_data, errstr);
}
bool k8s_audit_handler::handleGet(CivetServer *server, struct mg_connection *conn)
{
mg_send_http_error(conn, 405, "GET method not allowed");
return true;
}
// The version in CivetServer.cpp has valgrind complaints due to
// unguarded initialization of c++ string from buffer.
static void get_post_data(struct mg_connection *conn, std::string &postdata)
{
mg_lock_connection(conn);
char buf[2048];
int r = mg_read(conn, buf, sizeof(buf));
while(r > 0)
{
postdata.append(buf, r);
r = mg_read(conn, buf, sizeof(buf));
}
mg_unlock_connection(conn);
}
bool k8s_audit_handler::handlePost(CivetServer *server, struct mg_connection *conn)
{
// Ensure that the content-type is application/json
const char *ct = server->getHeader(conn, string("Content-Type"));
// content type *must* start with application/json
if(ct == NULL || strncmp(ct, "application/json", strlen("application/json")) != 0)
{
mg_send_http_error(conn, 400, "Wrong Content Type");
return true;
}
std::string post_data;
get_post_data(conn, post_data);
std::string errstr;
if(!accept_uploaded_data(post_data, errstr))
{
errstr = "Bad Request: " + errstr;
mg_send_http_error(conn, 400, "%s", errstr.c_str());
return true;
}
const std::string ok_body = "<html><body>Ok</body></html>";
mg_send_http_ok(conn, "text/html", ok_body.size());
mg_printf(conn, "%s", ok_body.c_str());
return true;
}
falco_webserver::falco_webserver():
m_config(NULL)
{
}
#include <atomic>
falco_webserver::~falco_webserver()
{
stop();
stop();
}
void falco_webserver::init(std::shared_ptr<falco_configuration> config,
std::shared_ptr<falco_engine> engine,
std::shared_ptr<falco_outputs> outputs,
std::size_t k8s_audit_event_source_idx)
void falco_webserver::start(
uint32_t listen_port,
std::string& healthz_endpoint,
std::string &ssl_certificate,
bool ssl_enabled)
{
m_config = config;
m_engine = engine;
m_outputs = outputs;
m_k8s_audit_event_source_idx = k8s_audit_event_source_idx;
}
if (m_running)
{
throw falco_exception(
"attempted restarting webserver without stopping it first");
}
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// allocate and configure server
if (ssl_enabled)
{
m_server = new httplib::SSLServer(
ssl_certificate.c_str(),
ssl_certificate.c_str());
}
else
{
m_server = new httplib::Server();
}
void falco_webserver::start()
{
if(m_server)
{
stop();
}
// setup healthz endpoint
m_server->Get(healthz_endpoint,
[](const httplib::Request &, httplib::Response &res) {
res.set_content("{\"status\": \"ok\"}", "application/json");
});
if(!m_config)
{
throw falco_exception("No config provided to webserver");
}
// run server in a separate thread
if (!m_server->is_valid())
{
delete m_server;
m_server = NULL;
throw falco_exception("invalid webserver configuration");
}
if(!m_engine)
{
throw falco_exception("No engine provided to webserver");
}
std::atomic<bool> failed;
failed.store(false, std::memory_order_relaxed);
m_server_thread = std::thread([this, listen_port, &failed]
{
try
{
this->m_server->listen("localhost", listen_port);
}
catch(std::exception &e)
{
falco_logger::log(
LOG_ERR,
"falco_webserver: " + string(e.what()) + "\n");
}
failed.store(true, std::memory_order_release);
});
if(!m_outputs)
{
throw falco_exception("No outputs provided to webserver");
}
std::vector<std::string> cpp_options = {
"num_threads", to_string(1)};
if(m_config->m_webserver_ssl_enabled)
{
cpp_options.push_back("listening_ports");
cpp_options.push_back(to_string(m_config->m_webserver_listen_port) + "s");
cpp_options.push_back("ssl_certificate");
cpp_options.push_back(m_config->m_webserver_ssl_certificate);
}
else
{
cpp_options.push_back("listening_ports");
cpp_options.push_back(to_string(m_config->m_webserver_listen_port));
}
try
{
m_server = make_unique<CivetServer>(cpp_options);
}
catch(CivetException &e)
{
throw falco_exception(std::string("Could not create embedded webserver: ") + e.what());
}
if(!m_server->getContext())
{
throw falco_exception("Could not create embedded webserver");
}
m_k8s_audit_handler = make_unique<k8s_audit_handler>(m_engine, m_outputs, m_k8s_audit_event_source_idx);
m_server->addHandler(m_config->m_webserver_k8s_audit_endpoint, *m_k8s_audit_handler);
m_k8s_healthz_handler = make_unique<k8s_healthz_handler>();
m_server->addHandler(m_config->m_webserver_k8s_healthz_endpoint, *m_k8s_healthz_handler);
// wait for the server to actually start up
// note: is_running() is atomic
while (!m_server->is_running() && !failed.load(std::memory_order_acquire))
{
std::this_thread::yield();
}
m_running = true;
if (failed.load(std::memory_order_acquire))
{
stop();
throw falco_exception("an error occurred while starting webserver");
}
}
void falco_webserver::stop()
{
if(m_server)
{
m_server = NULL;
m_k8s_audit_handler = NULL;
m_k8s_healthz_handler = NULL;
}
if (m_running)
{
if (m_server != NULL)
{
m_server->stop();
}
if(m_server_thread.joinable())
{
m_server_thread.join();
}
if (m_server != NULL)
{
delete m_server;
m_server = NULL;
}
m_running = false;
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2019 The Falco Authors.
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,71 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <memory>
#include "CivetServer.h"
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>
#include <thread>
#include "configuration.h"
#include "falco_engine.h"
#include "falco_outputs.h"
class k8s_audit_handler : public CivetHandler
{
public:
k8s_audit_handler(std::shared_ptr<falco_engine> engine, std::shared_ptr<falco_outputs> outputs, std::size_t k8s_audit_event_source_idx);
virtual ~k8s_audit_handler();
bool handleGet(CivetServer *server, struct mg_connection *conn);
bool handlePost(CivetServer *server, struct mg_connection *conn);
static bool accept_data(std::shared_ptr<falco_engine> engine,
std::shared_ptr<falco_outputs> outputs,
std::size_t k8s_audit_event_source_idx,
std::string &post_data, std::string &errstr);
private:
std::shared_ptr<falco_engine> m_engine;
std::shared_ptr<falco_outputs> m_outputs;
std::size_t m_k8s_audit_event_source_idx;
bool accept_uploaded_data(std::string &post_data, std::string &errstr);
};
class k8s_healthz_handler : public CivetHandler
{
public:
k8s_healthz_handler()
{
}
virtual ~k8s_healthz_handler()
{
}
bool handleGet(CivetServer *server, struct mg_connection *conn);
};
class falco_webserver
{
public:
falco_webserver();
virtual ~falco_webserver();
void init(std::shared_ptr<falco_configuration> config,
std::shared_ptr<falco_engine> engine,
std::shared_ptr<falco_outputs> outputs,
std::size_t k8s_audit_event_source_idx);
void start();
void stop();
virtual void start(
uint32_t listen_port,
std::string& healthz_endpoint,
std::string &ssl_certificate,
bool ssl_enabled);
virtual void stop();
private:
std::shared_ptr<falco_engine> m_engine;
std::shared_ptr<falco_configuration> m_config;
std::shared_ptr<falco_outputs> m_outputs;
std::size_t m_k8s_audit_event_source_idx;
unique_ptr<CivetServer> m_server;
unique_ptr<k8s_audit_handler> m_k8s_audit_handler;
unique_ptr<k8s_healthz_handler> m_k8s_healthz_handler;
bool m_running = false;
httplib::Server* m_server = NULL;
std::thread m_server_thread;
};