Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support arbitrary-depth nested values in YAML configuration #1792

Merged
21 changes: 18 additions & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,27 @@
# the License.
#
if(MINIMAL_BUILD)
set(FALCO_TESTS_SOURCES test_base.cpp engine/test_token_bucket.cpp engine/test_rulesets.cpp engine/test_falco_utils.cpp)
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_token_bucket.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
falco/test_configuration.cpp
)
else()
set(FALCO_TESTS_SOURCES test_base.cpp engine/test_token_bucket.cpp engine/test_rulesets.cpp engine/test_falco_utils.cpp falco/test_webserver.cpp)
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_token_bucket.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
falco/test_configuration.cpp
falco/test_webserver.cpp
)
endif()

set(FALCO_TESTED_LIBRARIES falco_engine)
set(FALCO_TESTED_LIBRARIES falco_engine ${YAMLCPP_LIB})

SET(FALCO_TESTS_ARGUMENTS "" CACHE STRING "Test arguments to pass to the Falco test suite")

Expand Down
106 changes: 106 additions & 0 deletions tests/falco/test_configuration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
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.
*/
#include "configuration.h"
#include <catch.hpp>

string sample_yaml =
"base_value:\n"
" id: 1\n"
" name: 'sample_name'\n"
" subvalue:\n"
" subvalue2:\n"
" boolean: true\n"
"base_value_2:\n"
" sample_list:\n"
" - elem1\n"
" - elem2\n"
" - elem3\n"
;

TEST_CASE("configuration must load YAML data", "[configuration]")
{
yaml_configuration conf;

SECTION("broken YAML")
{
string sample_broken_yaml = sample_yaml + " / bad_symbol";
REQUIRE_THROWS(conf.load_from_string(sample_broken_yaml));
}

SECTION("valid YAML")
{
REQUIRE_NOTHROW(conf.load_from_string(sample_yaml));
}

SECTION("clearing and reloading")
{
conf.load_from_string(sample_yaml);
REQUIRE(conf.is_defined("base_value") == true);
conf.clear();
REQUIRE(conf.is_defined("base_value") == false);
conf.load_from_string(sample_yaml);
REQUIRE(conf.is_defined("base_value") == true);
}
}

TEST_CASE("configuration must read YAML fields", "[configuration]")
{
yaml_configuration conf;
conf.load_from_string(sample_yaml);

SECTION("base level")
{
REQUIRE(conf.is_defined("base_value") == true);
REQUIRE(conf.is_defined("base_value_2") == true);
REQUIRE(conf.is_defined("unknown_base_value") == false);
}

SECTION("arbitrary depth nesting")
{
REQUIRE(conf.get_scalar<int>("base_value.id", -1) == 1);
REQUIRE(conf.get_scalar<string>("base_value.name", "none") == "sample_name");
REQUIRE(conf.get_scalar<bool>("base_value.subvalue.subvalue2.boolean", false) == true);
}

SECTION("list field elements")
{
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[0]", "none") == "elem1");
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[1]", "none") == "elem2");
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[2]", "none") == "elem3");
}

SECTION("sequence")
{
vector<string> seq;
conf.get_sequence(seq, "base_value_2.sample_list");
REQUIRE(seq.size() == 3);
REQUIRE(seq[0] == "elem1");
REQUIRE(seq[1] == "elem2");
REQUIRE(seq[2] == "elem3");
}
}

TEST_CASE("configuration must modify YAML fields", "[configuration]")
{
string key = "base_value.subvalue.subvalue2.boolean";
yaml_configuration conf;
conf.load_from_string(sample_yaml);
REQUIRE(conf.get_scalar<bool>(key, false) == true);
conf.set_scalar<bool>(key, false);
REQUIRE(conf.get_scalar<bool>(key, true) == false);
conf.set_scalar<bool>(key, true);
REQUIRE(conf.get_scalar<bool>(key, false) == true);
}
94 changes: 47 additions & 47 deletions userspace/falco/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ falco_configuration::~falco_configuration()
void falco_configuration::init(string conf_filename, list<string> &cmdline_options)
{
string m_config_file = conf_filename;
m_config = new yaml_configuration(m_config_file);
m_config = new yaml_configuration();
try
{
m_config->load_from_file(m_config_file);
}
catch(const std::exception& e)
{
std::cerr << "Cannot read config file (" + m_config_file + "): " + e.what() + "\n";
throw e;
}

init_cmdline_options(cmdline_options);

Expand All @@ -78,60 +87,60 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio

falco::outputs::config file_output;
file_output.name = "file";
if(m_config->get_scalar<bool>("file_output", "enabled", false))
if(m_config->get_scalar<bool>("file_output.enabled", false))
{
string filename, keep_alive;
filename = m_config->get_scalar<string>("file_output", "filename", "");
filename = m_config->get_scalar<string>("file_output.filename", "");
if(filename == string(""))
{
throw logic_error("Error reading config file (" + m_config_file + "): file output enabled but no filename in configuration block");
}
file_output.options["filename"] = filename;

keep_alive = m_config->get_scalar<string>("file_output", "keep_alive", "");
keep_alive = m_config->get_scalar<string>("file_output.keep_alive", "");
file_output.options["keep_alive"] = keep_alive;

m_outputs.push_back(file_output);
}

falco::outputs::config stdout_output;
stdout_output.name = "stdout";
if(m_config->get_scalar<bool>("stdout_output", "enabled", false))
if(m_config->get_scalar<bool>("stdout_output.enabled", false))
{
m_outputs.push_back(stdout_output);
}

falco::outputs::config syslog_output;
syslog_output.name = "syslog";
if(m_config->get_scalar<bool>("syslog_output", "enabled", false))
if(m_config->get_scalar<bool>("syslog_output.enabled", false))
{
m_outputs.push_back(syslog_output);
}

falco::outputs::config program_output;
program_output.name = "program";
if(m_config->get_scalar<bool>("program_output", "enabled", false))
if(m_config->get_scalar<bool>("program_output.enabled", false))
{
string program, keep_alive;
program = m_config->get_scalar<string>("program_output", "program", "");
program = m_config->get_scalar<string>("program_output.program", "");
if(program == string(""))
{
throw logic_error("Error reading config file (" + m_config_file + "): program output enabled but no program in configuration block");
}
program_output.options["program"] = program;

keep_alive = m_config->get_scalar<string>("program_output", "keep_alive", "");
keep_alive = m_config->get_scalar<string>("program_output.keep_alive", "");
program_output.options["keep_alive"] = keep_alive;

m_outputs.push_back(program_output);
}

falco::outputs::config http_output;
http_output.name = "http";
if(m_config->get_scalar<bool>("http_output", "enabled", false))
if(m_config->get_scalar<bool>("http_output.enabled", false))
{
string url;
url = m_config->get_scalar<string>("http_output", "url", "");
url = m_config->get_scalar<string>("http_output.url", "");

if(url == string(""))
{
Expand All @@ -142,22 +151,22 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
m_outputs.push_back(http_output);
}

m_grpc_enabled = m_config->get_scalar<bool>("grpc", "enabled", false);
m_grpc_bind_address = m_config->get_scalar<string>("grpc", "bind_address", "0.0.0.0:5060");
m_grpc_threadiness = m_config->get_scalar<uint32_t>("grpc", "threadiness", 0);
m_grpc_enabled = m_config->get_scalar<bool>("grpc.enabled", false);
m_grpc_bind_address = m_config->get_scalar<string>("grpc.bind_address", "0.0.0.0:5060");
m_grpc_threadiness = m_config->get_scalar<uint32_t>("grpc.threadiness", 0);
if(m_grpc_threadiness == 0)
{
m_grpc_threadiness = falco::utils::hardware_concurrency();
}
// todo > else limit threadiness to avoid oversubscription?
m_grpc_private_key = m_config->get_scalar<string>("grpc", "private_key", "/etc/falco/certs/server.key");
m_grpc_cert_chain = m_config->get_scalar<string>("grpc", "cert_chain", "/etc/falco/certs/server.crt");
m_grpc_root_certs = m_config->get_scalar<string>("grpc", "root_certs", "/etc/falco/certs/ca.crt");
m_grpc_private_key = m_config->get_scalar<string>("grpc.private_key", "/etc/falco/certs/server.key");
m_grpc_cert_chain = m_config->get_scalar<string>("grpc.cert_chain", "/etc/falco/certs/server.crt");
m_grpc_root_certs = m_config->get_scalar<string>("grpc.root_certs", "/etc/falco/certs/ca.crt");

falco::outputs::config grpc_output;
grpc_output.name = "grpc";
// gRPC output is enabled only if gRPC server is enabled too
if(m_config->get_scalar<bool>("grpc_output", "enabled", true) && m_grpc_enabled)
if(m_config->get_scalar<bool>("grpc_output.enabled", true) && m_grpc_enabled)
{
m_outputs.push_back(grpc_output);
}
Expand All @@ -173,8 +182,8 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio

m_output_timeout = m_config->get_scalar<uint32_t>("output_timeout", 2000);

m_notifications_rate = m_config->get_scalar<uint32_t>("outputs", "rate", 1);
m_notifications_max_burst = m_config->get_scalar<uint32_t>("outputs", "max_burst", 1000);
m_notifications_rate = m_config->get_scalar<uint32_t>("outputs.rate", 1);
m_notifications_max_burst = m_config->get_scalar<uint32_t>("outputs.max_burst", 1000);

string priority = m_config->get_scalar<string>("priority", "debug");
vector<string>::iterator it;
Expand All @@ -195,15 +204,15 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
falco_logger::log_stderr = m_config->get_scalar<bool>("log_stderr", false);
falco_logger::log_syslog = m_config->get_scalar<bool>("log_syslog", true);

m_webserver_enabled = m_config->get_scalar<bool>("webserver", "enabled", false);
m_webserver_listen_port = m_config->get_scalar<uint32_t>("webserver", "listen_port", 8765);
m_webserver_k8s_audit_endpoint = m_config->get_scalar<string>("webserver", "k8s_audit_endpoint", "/k8s-audit");
m_webserver_k8s_healthz_endpoint = m_config->get_scalar<string>("webserver", "k8s_healthz_endpoint", "/healthz");
m_webserver_ssl_enabled = m_config->get_scalar<bool>("webserver", "ssl_enabled", false);
m_webserver_ssl_certificate = m_config->get_scalar<string>("webserver", "ssl_certificate", "/etc/falco/falco.pem");
m_webserver_enabled = m_config->get_scalar<bool>("webserver.enabled", false);
m_webserver_listen_port = m_config->get_scalar<uint32_t>("webserver.listen_port", 8765);
m_webserver_k8s_audit_endpoint = m_config->get_scalar<string>("webserver.k8s_audit_endpoint", "/k8s-audit");
m_webserver_k8s_healthz_endpoint = m_config->get_scalar<string>("webserver.k8s_healthz_endpoint", "/healthz");
m_webserver_ssl_enabled = m_config->get_scalar<bool>("webserver.ssl_enabled", false);
m_webserver_ssl_certificate = m_config->get_scalar<string>("webserver.ssl_certificate", "/etc/falco/falco.pem");

std::list<string> syscall_event_drop_acts;
m_config->get_sequence(syscall_event_drop_acts, "syscall_event_drops", "actions");
m_config->get_sequence(syscall_event_drop_acts, "syscall_event_drops.actions");

for(std::string &act : syscall_event_drop_acts)
{
Expand Down Expand Up @@ -242,37 +251,36 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
m_syscall_evt_drop_actions.insert(syscall_evt_drop_action::IGNORE);
}

m_syscall_evt_drop_threshold = m_config->get_scalar<double>("syscall_event_drops", "threshold", .1);
m_syscall_evt_drop_threshold = m_config->get_scalar<double>("syscall_event_drops.threshold", .1);
if(m_syscall_evt_drop_threshold < 0 || m_syscall_evt_drop_threshold > 1)
{
throw logic_error("Error reading config file (" + m_config_file + "): syscall event drops threshold must be a double in the range [0, 1]");
}
m_syscall_evt_drop_rate = m_config->get_scalar<double>("syscall_event_drops", "rate", .03333);
m_syscall_evt_drop_max_burst = m_config->get_scalar<double>("syscall_event_drops", "max_burst", 1);
m_syscall_evt_simulate_drops = m_config->get_scalar<bool>("syscall_event_drops", "simulate_drops", false);
m_syscall_evt_drop_rate = m_config->get_scalar<double>("syscall_event_drops.rate", .03333);
m_syscall_evt_drop_max_burst = m_config->get_scalar<double>("syscall_event_drops.max_burst", 1);
m_syscall_evt_simulate_drops = m_config->get_scalar<bool>("syscall_event_drops.simulate_drops", false);

m_syscall_evt_timeout_max_consecutives = m_config->get_scalar<uint32_t>("syscall_event_timeouts", "max_consecutives", 1000);
m_syscall_evt_timeout_max_consecutives = m_config->get_scalar<uint32_t>("syscall_event_timeouts.max_consecutives", 1000);
if(m_syscall_evt_timeout_max_consecutives == 0)
{
throw logic_error("Error reading config file(" + m_config_file + "): the maximum consecutive timeouts without an event must be an unsigned integer > 0");
}

m_metadata_download_max_mb = m_config->get_scalar<uint32_t>("metadata_download", "max_mb", 100);
m_metadata_download_max_mb = m_config->get_scalar<uint32_t>("metadata_download.max_mb", 100);
if(m_metadata_download_max_mb > 1024)
{
throw logic_error("Error reading config file(" + m_config_file + "): metadata download maximum size should be < 1024 Mb");
}
m_metadata_download_chunk_wait_us = m_config->get_scalar<uint32_t>("metadata_download", "chunk_wait_us", 1000);
m_metadata_download_watch_freq_sec = m_config->get_scalar<uint32_t>("metadata_download", "watch_freq_sec", 1);
m_metadata_download_chunk_wait_us = m_config->get_scalar<uint32_t>("metadata_download.chunk_wait_us", 1000);
m_metadata_download_watch_freq_sec = m_config->get_scalar<uint32_t>("metadata_download.watch_freq_sec", 1);
if(m_metadata_download_watch_freq_sec == 0)
{
throw logic_error("Error reading config file(" + m_config_file + "): metadata download watch frequency seconds must be an unsigned integer > 0");
}

std::set<std::string> load_plugins;

YAML::Node load_plugins_node;
m_config->get_node(load_plugins_node, "load_plugins");
bool load_plugins_node_defined = m_config->is_defined("load_plugins");

m_config->get_sequence<set<string>>(load_plugins, "load_plugins");

Expand All @@ -293,7 +301,7 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
// If load_plugins was not specified at all, every
// plugin is added. Otherwise, the plugin must be in
// the load_plugins list.
if(!load_plugins_node.IsDefined() || load_plugins.find(p.m_name) != load_plugins.end())
if(!load_plugins_node_defined || load_plugins.find(p.m_name) != load_plugins.end())
{
m_plugins.push_back(p);
}
Expand Down Expand Up @@ -389,19 +397,11 @@ void falco_configuration::init_cmdline_options(list<string> &cmdline_options)
void falco_configuration::set_cmdline_option(const string &opt)
{
pair<string, string> keyval;
pair<string, string> subkey;

if(!split(opt, '=', keyval))
{
throw logic_error("Error parsing config option \"" + opt + "\". Must be of the form key=val or key.subkey=val");
}

if(split(keyval.first, '.', subkey))
{
m_config->set_scalar(subkey.first, subkey.second, keyval.second);
}
else
{
m_config->set_scalar(keyval.first, keyval.second);
}
m_config->set_scalar(keyval.first, keyval.second);
}
Loading