From b91ff34b9762ca15610debf81c41d19dfabae0c5 Mon Sep 17 00:00:00 2001 From: Jason Dellaluce Date: Wed, 23 Mar 2022 13:37:30 +0000 Subject: [PATCH] refactor: drop civetweb dependency and implement healtz using cpp-httplib Signed-off-by: Jason Dellaluce --- CMakeLists.txt | 4 +- cmake/modules/civetweb.cmake | 53 --- cmake/modules/cpp-httplib.cmake | 32 ++ tests/CMakeLists.txt | 35 +- tests/falco/test_webserver.cpp | 28 -- userspace/falco/CMakeLists.txt | 6 +- .../falco/app_actions/start_webserver.cpp | 6 +- userspace/falco/falco.cpp | 1 - userspace/falco/webserver.cpp | 320 +++++------------- userspace/falco/webserver.h | 73 +--- 10 files changed, 145 insertions(+), 413 deletions(-) delete mode 100644 cmake/modules/civetweb.cmake create mode 100644 cmake/modules/cpp-httplib.cmake delete mode 100644 tests/falco/test_webserver.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eec21b91..cd80f904 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,8 +148,8 @@ if(NOT MINIMAL_BUILD) # libcurl include(curl) - # civetweb - include(civetweb) + # cpp-httlib + include(cpp-httplib) endif() include(cxxopts) diff --git a/cmake/modules/civetweb.cmake b/cmake/modules/civetweb.cmake deleted file mode 100644 index 31ba6745..00000000 --- a/cmake/modules/civetweb.cmake +++ /dev/null @@ -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() \ No newline at end of file diff --git a/cmake/modules/cpp-httplib.cmake b/cmake/modules/cpp-httplib.cmake new file mode 100644 index 00000000..5df1821e --- /dev/null +++ b/cmake/modules/cpp-httplib.cmake @@ -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() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ca730376..3bee3e3f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/falco/test_webserver.cpp b/tests/falco/test_webserver.cpp deleted file mode 100644 index 0e997b3c..00000000 --- a/tests/falco/test_webserver.cpp +++ /dev/null @@ -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 - -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); -} \ No newline at end of file diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index eefd4d68..e6222bdb 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -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() diff --git a/userspace/falco/app_actions/start_webserver.cpp b/userspace/falco/app_actions/start_webserver.cpp index 62cd895c..211c5f7f 100644 --- a/userspace/falco/app_actions/start_webserver.cpp +++ b/userspace/falco/app_actions/start_webserver.cpp @@ -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; diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index e72bd065..7ede779b 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -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()); } diff --git a/userspace/falco/webserver.cpp b/userspace/falco/webserver.cpp index d009310f..6649eaca 100644 --- a/userspace/falco/webserver.cpp +++ b/userspace/falco/webserver.cpp @@ -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 -#include - -#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 engine, std::shared_ptr 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 engine, - std::shared_ptr outputs, - std::size_t k8s_audit_event_source_idx, - std::string &data, - std::string &errstr) -{ - std::list 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 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 = "Ok"; - 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 falco_webserver::~falco_webserver() { - stop(); + stop(); } -void falco_webserver::init(std::shared_ptr config, - std::shared_ptr engine, - std::shared_ptr 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 -std::unique_ptr make_unique(Args &&... args) -{ - return std::unique_ptr(new T(std::forward(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 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 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(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(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(); - 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; + } } diff --git a/userspace/falco/webserver.h b/userspace/falco/webserver.h index 5d955053..bacf8f3b 100644 --- a/userspace/falco/webserver.h +++ b/userspace/falco/webserver.h @@ -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 - -#include "CivetServer.h" - +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include +#include #include "configuration.h" -#include "falco_engine.h" -#include "falco_outputs.h" - -class k8s_audit_handler : public CivetHandler -{ -public: - k8s_audit_handler(std::shared_ptr engine, std::shared_ptr 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 engine, - std::shared_ptr outputs, - std::size_t k8s_audit_event_source_idx, - std::string &post_data, std::string &errstr); - -private: - std::shared_ptr m_engine; - std::shared_ptr 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 config, - std::shared_ptr engine, - std::shared_ptr 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 m_engine; - std::shared_ptr m_config; - std::shared_ptr m_outputs; - std::size_t m_k8s_audit_event_source_idx; - unique_ptr m_server; - unique_ptr m_k8s_audit_handler; - unique_ptr m_k8s_healthz_handler; + bool m_running = false; + httplib::Server* m_server = NULL; + std::thread m_server_thread; };