From c0520123d41129cd949d19c931d2684bf64c9a1b Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sun, 31 Aug 2025 10:47:40 +0200 Subject: [PATCH] refactor: run the webserver unprivileged The webserver was running in a thread with the same privileges as the falco process. In order to make running it more secure, use fork rather than thread and then change the user and group id to unprivileged values. Signed-off-by: Samuel Gaist --- falco.yaml | 3 ++ userspace/falco/config_json_schema.h | 6 +++ userspace/falco/configuration.cpp | 2 + userspace/falco/configuration.h | 2 + userspace/falco/webserver.cpp | 56 ++++++++++++++++++---------- userspace/falco/webserver.h | 1 + 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/falco.yaml b/falco.yaml index 61b4366d..a9e02737 100644 --- a/falco.yaml +++ b/falco.yaml @@ -963,6 +963,9 @@ webserver: prometheus_metrics_enabled: false ssl_enabled: false ssl_certificate: /etc/falco/falco.pem + # User and group id under which the server should run + uid: 1000 + gid: 1000 ############################################################################## # Falco logging / alerting / metrics related to software functioning (basic) # diff --git a/userspace/falco/config_json_schema.h b/userspace/falco/config_json_schema.h index 70a4c11b..5c491a80 100644 --- a/userspace/falco/config_json_schema.h +++ b/userspace/falco/config_json_schema.h @@ -796,6 +796,12 @@ const char config_schema_string[] = LONG_STRING_CONST( }, "ssl_certificate": { "type": "string" + }, + "uid": { + "type": "integer" + }, + "gid": { + "type": "integer" } }, "minProperties": 1, diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index bde20664..f2f6bc21 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -537,6 +537,8 @@ void falco_configuration::load_yaml(const std::string &config_name) { } m_webserver_config.m_prometheus_metrics_enabled = m_config.get_scalar("webserver.prometheus_metrics_enabled", false); + m_webserver_config.m_uid = m_config.get_scalar("webserver.uid", 1000); + m_webserver_config.m_gid = m_config.get_scalar("webserver.gid", 1000); std::list syscall_event_drop_acts; m_config.get_sequence(syscall_event_drop_acts, "syscall_event_drops.actions"); diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index f7a9c3a0..79e84564 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -95,6 +95,8 @@ public: bool m_ssl_enabled = false; std::string m_ssl_certificate; bool m_prometheus_metrics_enabled = false; + uint32_t m_uid = 1000; + uint32_t m_gid = 1000; }; enum class rule_selection_operation { enable, disable }; diff --git a/userspace/falco/webserver.cpp b/userspace/falco/webserver.cpp index 5b83162a..e071f4db 100644 --- a/userspace/falco/webserver.cpp +++ b/userspace/falco/webserver.cpp @@ -21,6 +21,7 @@ limitations under the License. #include "app/state.h" #include "versions_info.h" #include +#include falco_webserver::~falco_webserver() { stop(); @@ -58,47 +59,62 @@ void falco_webserver::start(const falco::app::state &state, res.set_content(versions_json_str, "application/json"); }); - // run server in a separate thread if(!m_server->is_valid()) { m_server = nullptr; throw falco_exception("invalid webserver configuration"); } m_failed.store(false, std::memory_order_release); - m_server_thread = std::thread([this, webserver_config] { + + // fork the server + m_pid = fork(); + + if(m_pid == 0) { + falco_logger::log(falco_logger::level::INFO, "Webserver: forked\n"); + int res = setgid(webserver_config.m_uid); + if(res != NOERROR) { + throw falco_exception("Webserver: an error occurred while setting group id: " + + std::to_string(errno)); + } + res = setuid(webserver_config.m_gid); + if(res != NOERROR) { + throw falco_exception("Webserver: an error occurred while setting user id: " + + std::to_string(errno)); + } + falco_logger::log(falco_logger::level::INFO, + "Webserver: fork running as " + std::to_string(webserver_config.m_uid) + + ":" + std::to_string(webserver_config.m_gid) + "\n"); try { this->m_server->listen(webserver_config.m_listen_address, webserver_config.m_listen_port); } catch(std::exception &e) { falco_logger::log(falco_logger::level::ERR, - "falco_webserver: " + std::string(e.what()) + "\n"); + "Webserver: " + std::string(e.what()) + "\n"); + m_failed.store(true, std::memory_order_release); } - this->m_failed.store(true, std::memory_order_release); - }); - - // wait for the server to actually start up - // note: is_running() is atomic - while(!m_server->is_running() && !m_failed.load(std::memory_order_acquire)) { - std::this_thread::yield(); - } - m_running = true; - if(m_failed.load(std::memory_order_acquire)) { - stop(); - throw falco_exception("an error occurred while starting webserver"); + } else if(m_pid < 0) { + throw falco_exception("Webserver: an error occurred while forking webserver"); } } void falco_webserver::stop() { - if(m_running) { + if(m_pid > 0) { + falco_logger::log(falco_logger::level::INFO, "Webserver: stopping server\n"); if(m_server != nullptr) { m_server->stop(); } - if(m_server_thread.joinable()) { - m_server_thread.join(); + falco_logger::log(falco_logger::level::INFO, "Webserver: killing fork\n"); + int res = kill(m_pid, SIGTERM); + if(res != 0) { + throw falco_exception("Webserver: an error occurred while killing fork: " + + std::to_string(errno)); } - m_server = nullptr; - m_running = false; + m_pid = 0; + falco_logger::log(falco_logger::level::INFO, "Webserver: stopping fork done\n"); } + + m_server = nullptr; + m_running = false; } void falco_webserver::enable_prometheus_metrics(const falco::app::state &state) { diff --git a/userspace/falco/webserver.h b/userspace/falco/webserver.h index e5d97e58..ac02dc78 100644 --- a/userspace/falco/webserver.h +++ b/userspace/falco/webserver.h @@ -47,4 +47,5 @@ private: std::unique_ptr m_server = nullptr; std::thread m_server_thread; std::atomic m_failed; + int m_pid = -1; };