Compare commits

...

9 Commits

Author SHA1 Message Date
Samuel Gaist
fd13688ad7 refactor(webserver): improve failure handling
If starting the server throws for some reason,
the result will be instantly shown.

Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-12 00:20:11 +01:00
Samuel Gaist
91d586900e fix: use nobody uid/gid as default values for webserver
Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-12 00:19:54 +01:00
Samuel Gaist
1ec2d74546 refactor: reword information
Improve readability

Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-10 11:47:50 +01:00
Samuel Gaist
ef495bc48e refactor: use configured listen address for liveness check
Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-10 11:44:03 +01:00
Samuel Gaist
ccbc4138e8 fix: use correct parameter for setgid and setuid
They were inverted.

Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-10 11:42:49 +01:00
Samuel Gaist
23ba06de51 fix: do not throw from a nothrow function
Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-09 22:48:05 +01:00
Samuel Gaist
b12eb3d85d fix: use std::strerror rather than just errno
Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-09 22:48:00 +01:00
Samuel Gaist
66e4c986ca refactor: use client to check if the web server is running
Using a local variable won't work since the server and the
main application don't share the same memory space anymore.

To check that the server is running, a call to the health
endpoint is used.

While the start of the server should be pretty fast, a
simple exponential backoff has been implemented to allow
for room in case the start is slowed for some reason.

Signed-off-by: Samuel Gaist <samuel.gaist@idiap.ch>
2026-02-09 22:47:16 +01:00
Samuel Gaist
be4d9bbc19 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 <samuel.gaist@idiap.ch>
2026-02-09 21:21:33 +01:00
6 changed files with 97 additions and 25 deletions

View File

@@ -921,6 +921,9 @@ webserver:
# $ cat certificate.pem key.pem > falco.pem $ sudo cp falco.pem /etc/falco/falco.pem
# ```
ssl_certificate: /etc/falco/falco.pem
# User and group id under which the server should run
uid: 65534
gid: 65534
##############################################################################
# Falco logging / alerting / metrics related to software functioning (basic) #

View File

@@ -737,6 +737,12 @@ const char config_schema_string[] = LONG_STRING_CONST(
},
"ssl_certificate": {
"type": "string"
},
"uid": {
"type": "integer"
},
"gid": {
"type": "integer"
}
},
"minProperties": 1,

View File

@@ -489,6 +489,8 @@ void falco_configuration::load_yaml(const std::string &config_name) {
}
m_webserver_config.m_prometheus_metrics_enabled =
m_config.get_scalar<bool>("webserver.prometheus_metrics_enabled", false);
m_webserver_config.m_uid = m_config.get_scalar<uint32_t>("webserver.uid", 65534);
m_webserver_config.m_gid = m_config.get_scalar<uint32_t>("webserver.gid", 65534);
std::list<std::string> syscall_event_drop_acts;
m_config.get_sequence(syscall_event_drop_acts, "syscall_event_drops.actions");

View File

@@ -84,6 +84,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 };

View File

@@ -21,6 +21,17 @@ limitations under the License.
#include "app/state.h"
#include "versions_info.h"
#include <atomic>
#include <signal.h>
#include <sys/wait.h>
using namespace std::chrono_literals;
namespace {
std::function<void(int)> sigchld_func;
void sigchld_handler(int signal) {
sigchld_func(signal);
}
} // namespace
falco_webserver::~falco_webserver() {
stop();
@@ -58,47 +69,95 @@ 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] {
sigchld_func = [&](int signal) {
wait(nullptr);
m_child_stopped = true;
};
signal(SIGCHLD, sigchld_handler);
// fork the server
m_pid = fork();
if(m_pid < 0) {
throw falco_exception("webserver: an error occurred while forking webserver");
} else if(m_pid == 0) {
falco_logger::log(falco_logger::level::INFO, "Webserver: forked\n");
int res = setgid(webserver_config.m_gid);
if(res != NOERROR) {
throw falco_exception(
std::string("webserver: an error occurred while setting group id: ") +
std::strerror(errno));
}
res = setuid(webserver_config.m_uid);
if(res != NOERROR) {
throw falco_exception(
std::string("webserver: an error occurred while setting user id: ") +
std::strerror(errno));
}
falco_logger::log(falco_logger::level::INFO,
"Webserver: process 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 error: " + std::string(e.what()) + "\n");
throw;
}
this->m_failed.store(true, std::memory_order_release);
});
} else {
std::string schema = "http";
if(webserver_config.m_ssl_enabled) {
schema = "https";
}
std::string url = schema + "://" + webserver_config.m_listen_address + ":" +
std::to_string(webserver_config.m_listen_port);
httplib::Client cli(url);
// 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");
const int max_retries = 10;
const std::chrono::seconds delay = 1s;
int retry = 0;
m_running = false;
m_child_stopped = false;
while(retry++ < max_retries && !m_child_stopped) {
if(auto res = cli.Get(webserver_config.m_k8s_healthz_endpoint)) {
falco_logger::log(falco_logger::level::INFO, "Webserver: successfully started\n");
m_running = true;
break;
}
std::this_thread::sleep_for(delay * retry);
}
if(!m_running) {
throw falco_exception("webserver: the server is not running");
}
}
}
void falco_webserver::stop() {
if(m_running) {
if(m_server != nullptr) {
m_server->stop();
if(m_pid > 0) {
falco_logger::log(falco_logger::level::INFO,
"Webserver: terminating process " + std::to_string(m_pid) + "\n");
int res = kill(m_pid, SIGKILL);
if(res != 0) {
falco_logger::log(
falco_logger::level::ERR,
std::string("Webserver: an error occurred while terminating process: ") +
std::strerror(errno));
}
if(m_server_thread.joinable()) {
m_server_thread.join();
}
m_server = nullptr;
m_running = false;
waitpid(m_pid, nullptr, 0);
m_pid = -1;
falco_logger::log(falco_logger::level::INFO, "Webserver: stopping process done\n");
}
m_server = nullptr;
m_running = false;
m_child_stopped = true;
}
void falco_webserver::enable_prometheus_metrics(const falco::app::state &state) {

View File

@@ -44,7 +44,7 @@ public:
private:
bool m_running = false;
bool m_child_stopped = true;
std::unique_ptr<httplib::Server> m_server = nullptr;
std::thread m_server_thread;
std::atomic<bool> m_failed;
int m_pid = -1;
};