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

@ -148,8 +148,8 @@ if(NOT MINIMAL_BUILD)
# libcurl
include(curl)
# civetweb
include(civetweb)
# cpp-httlib
include(cpp-httplib)
endif()
include(cxxopts)

View File

@ -1,53 +0,0 @@
#
# Copyright (C) 2021 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. 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.
#
set(CIVETWEB_SRC "${PROJECT_BINARY_DIR}/civetweb-prefix/src/civetweb/")
set(CIVETWEB_LIB "${CIVETWEB_SRC}/install/lib/libcivetweb.a")
SET(CIVETWEB_CPP_LIB "${CIVETWEB_SRC}/install/lib/libcivetweb-cpp.a")
set(CIVETWEB_INCLUDE_DIR "${CIVETWEB_SRC}/install/include")
message(STATUS "Using bundled civetweb in '${CIVETWEB_SRC}'")
if (USE_BUNDLED_OPENSSL)
ExternalProject_Add(
civetweb
DEPENDS openssl
URL "https://github.com/civetweb/civetweb/archive/v1.15.tar.gz"
URL_HASH "SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9"
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCMAKE_INSTALL_LIBDIR=lib
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off
-DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=off
-DCIVETWEB_SERVE_NO_FILES=on
-DCMAKE_INSTALL_PREFIX=${CIVETWEB_SRC}/install
-DOPENSSL_ROOT_DIR:PATH=${OPENSSL_INSTALL_DIR}
-DOPENSSL_USE_STATIC_LIBS:BOOL=TRUE
BUILD_BYPRODUCTS ${CIVETWEB_LIB} ${CIVETWEB_CPP_LIB})
else()
ExternalProject_Add(
civetweb
URL "https://github.com/civetweb/civetweb/archive/v1.15.tar.gz"
URL_HASH "SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9"
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off
-DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=off
-DCIVETWEB_SERVE_NO_FILES=on
-DCMAKE_INSTALL_PREFIX=${CIVETWEB_SRC}/install
BUILD_BYPRODUCTS ${CIVETWEB_LIB} ${CIVETWEB_CPP_LIB})
endif()

View File

@ -0,0 +1,32 @@
#
# 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. 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.
#
#
# cpp-httplib (https://github.com/yhirose/cpp-httplib)
#
if(CPPHTTPLIB_INCLUDE)
# we already have cpp-httplib
else()
set(CPPHTTPLIB_SRC "${PROJECT_BINARY_DIR}/cpp-httplib-prefix/src/cpp-httplib")
set(CPPHTTPLIB_INCLUDE "${CPPHTTPLIB_SRC}")
message(STATUS "Using bundled cpp-httplib in '${CPPHTTPLIB_SRC}'")
ExternalProject_Add(cpp-httplib
PREFIX "${PROJECT_BINARY_DIR}/cpp-httplib-prefix"
URL "https://github.com/yhirose/cpp-httplib/archive/refs/tags/v0.10.4.tar.gz"
URL_HASH "SHA256=7719ff9f309c807dd8a574048764836b6a12bcb7d6ae9e129e7e4289cfdb4bd4"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
endif()

View File

@ -14,30 +14,16 @@
# License for the specific language governing permissions and limitations under
# the License.
#
if(MINIMAL_BUILD)
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
engine/test_filter_macro_resolver.cpp
engine/test_filter_evttype_resolver.cpp
engine/test_filter_warning_resolver.cpp
falco/test_configuration.cpp
)
else()
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
engine/test_filter_macro_resolver.cpp
engine/test_filter_evttype_resolver.cpp
engine/test_filter_warning_resolver.cpp
falco/test_configuration.cpp
falco/test_webserver.cpp
)
endif()
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
engine/test_filter_macro_resolver.cpp
engine/test_filter_evttype_resolver.cpp
engine/test_filter_warning_resolver.cpp
falco/test_configuration.cpp
)
set(FALCO_TESTED_LIBRARIES falco_engine ${YAMLCPP_LIB})
@ -76,7 +62,6 @@ if(FALCO_BUILD_TESTS)
"${PROJECT_SOURCE_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/falco"
"${YAMLCPP_INCLUDE_DIR}"
"${CIVETWEB_INCLUDE_DIR}"
"${PROJECT_SOURCE_DIR}/userspace/falco")
endif()
add_dependencies(falco_test catch2)

View File

@ -1,28 +0,0 @@
/*
Copyright (C) 2019 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.
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 "webserver.h"
#include <catch.hpp>
TEST_CASE("webserver must accept invalid data", "[!hide][webserver][k8s_audit_handler][accept_data]")
{
// falco_engine* engine = new falco_engine();
// falco_outputs* outputs = new falco_outputs(engine);
// std::string errstr;
// std::string input("{\"kind\": 0}");
//k8s_audit_handler::accept_data(engine, outputs, input, errstr);
REQUIRE(1 == 1);
}

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