From 79f984325649c3f24e6921d0d1f7312a055c1e10 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Tue, 10 May 2016 15:52:59 -0700 Subject: [PATCH] Clean up handling cmdline options wrt config file. Remove the old use of the '-o' command line option, it wasn't being used. Allow any config file option to be overridden on the command line, via --option/-o. These options are applied to the configuration object after reading the file, ensuring the command line options override anything in the config file. To support this, add some methods to yaml_configuration that allows you to set the value for a top level key or key + subkey, and methods to falco_configuration that allow providing a set of command line arguments alongside the config file. Ensure that any fatal error is always printed to stderr even if stderr logging is not enabled. This makes sure that falco won't silently exit on an error. This is especially important when daemonizing and when an initial fatal error occurs first. As a part of this, change all fatal errors to throw exceptions instead, so all fatal errors get routed through the exception handler. Improve daemonization by reopening stdin/stdout/stderr to /dev/null so you don't have to worry about writing to a closed stderr on exit. --- userspace/falco/configuration.cpp | 45 +++++++++++++++++++++- userspace/falco/configuration.h | 41 +++++++++++++++++++- userspace/falco/falco.cpp | 64 ++++++++++++++++--------------- 3 files changed, 116 insertions(+), 34 deletions(-) diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 74e5775e..4727d0dd 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -7,18 +7,22 @@ using namespace std; // If we don't have a configuration file, we just use stdout output and all other defaults -void falco_configuration::init() +void falco_configuration::init(std::list &cmdline_options) { + init_cmdline_options(cmdline_options); + output_config stdout_output; stdout_output.name = "stdout"; m_outputs.push_back(stdout_output); } -void falco_configuration::init(string conf_filename) +void falco_configuration::init(string conf_filename, std::list &cmdline_options) { string m_config_file = conf_filename; m_config = new yaml_configuration(m_config_file); + init_cmdline_options(cmdline_options); + m_rules_filename = m_config->get_scalar("rules_file", "/etc/falco_rules.yaml"); m_json_output = m_config->get_scalar("json_output", false); @@ -58,3 +62,40 @@ void falco_configuration::init(string conf_filename) falco_logger::log_stderr = m_config->get_scalar("log_stderr", false); falco_logger::log_syslog = m_config->get_scalar("log_syslog", true); } + +static bool split(const string &str, char delim, pair &parts) +{ + size_t pos; + + if ((pos = str.find_first_of(delim)) == string::npos) { + return false; + } + parts.first = str.substr(0, pos); + parts.second = str.substr(pos + 1); + + return true; +} + +void falco_configuration::init_cmdline_options(std::list &cmdline_options) +{ + for(const string &option : cmdline_options) + { + set_cmdline_option(option); + } +} + +void falco_configuration::set_cmdline_option(const std::string &opt) +{ + pair keyval; + pair subkey; + + if (! split(opt, '=', keyval)) { + throw sinsp_exception("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); + } +} diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 48d93c7e..d389aa37 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -56,6 +56,19 @@ public: return default_value; } + /** + * Set the top-level node identified by key to value + */ + template + void set_scalar(const std::string &key, const T& value) + { + auto node = m_root; + if (node.IsDefined()) + { + node[key] = value; + } + } + /** * Get a scalar value defined inside a 2 level nested structure like: * file_output: @@ -84,6 +97,19 @@ public: return default_value; } + /** + * Set the second-level node identified by key[key][subkey] to value. + */ + template + void set_scalar(const std::string& key, const std::string& subkey, const T& value) + { + auto node = m_root; + if (node.IsDefined()) + { + node[key][subkey] = value; + } + } + private: YAML::Node m_root; }; @@ -92,12 +118,23 @@ private: class falco_configuration { public: - void init(std::string conf_filename); - void init(); + void init(std::string conf_filename, std::list &cmdline_options); + void init(std::list &cmdline_options); + std::string m_rules_filename; bool m_json_output; std::vector m_outputs; private: + void init_cmdline_options(std::list &cmdline_options); + + /** + * Given a = specifier, set the appropriate option + * in the underlying yaml config. can contain '.' + * characters for nesting. Currently only 1- or 2- level keys + * are supported and only scalar values are supported. + */ + void set_cmdline_option(const std::string &spec); + yaml_configuration* m_config; }; diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 9dce811c..4967ee5d 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -29,19 +29,18 @@ extern "C" { #include -std::vector valid_output_names {"stdout", "syslog"}; - // // Program help // static void usage() { printf( - "Usage: falco [options] rules_filename\n\n" + "Usage: falco [options]\n\n" "Options:\n" " -h, --help Print this page\n" " -c Configuration file (default " FALCO_SOURCE_CONF_FILE ", " FALCO_INSTALL_CONF_FILE ")\n" - " -o Output type (options are 'stdout', 'syslog', default is 'stdout')\n" + " -o, --option = Set the value of option to . Overrides values in configuration file.\n" + " can be a two-part .\n" " -d, --daemon Run as a daemon\n" " -p, --pidfile When run as a daemon, write pid to specified file\n" " -e Read the events from (in .scap format) instead of tapping into live.\n" @@ -50,9 +49,26 @@ static void usage() ); } +static void display_fatal_err(const string &msg, bool daemon) +{ + falco_logger::log(LOG_ERR, msg); + + /** + * If stderr logging is not enabled, also log to stderr. When + * daemonized this will simply write to /dev/null. + */ + if (! falco_logger::log_stderr) + { + std::cerr << msg; + } +} + string lua_on_event = "on_event"; string lua_add_output = "add_output"; +// Splitting into key=value or key.subkey=value will be handled by configuration class. +std::list cmdline_options; + // // Event processing loop // @@ -194,7 +210,6 @@ int falco_init(int argc, char **argv) sinsp_evt::param_fmt event_buffer_format; int long_index = 0; string lua_main_filename; - string output_name = "syslog"; string scap_filename; string conf_filename; string rules_filename; @@ -207,14 +222,15 @@ int falco_init(int argc, char **argv) { {"help", no_argument, 0, 'h' }, {"daemon", no_argument, 0, 'd' }, - {"pidfile", required_argument, 0, 'd' }, + {"option", required_argument, 0, 'o'}, + {"pidfile", required_argument, 0, 'p' }, + {0, 0, 0, 0} }; try { inspector = new sinsp(); - bool valid; // // Parse the args @@ -232,12 +248,7 @@ int falco_init(int argc, char **argv) conf_filename = optarg; break; case 'o': - valid = std::find(valid_output_names.begin(), valid_output_names.end(), optarg) != valid_output_names.end(); - if (!valid) - { - throw sinsp_exception(string("Invalid output name ") + optarg); - } - output_name = optarg; + cmdline_options.push_back(optarg); break; case 'e': scap_filename = optarg; @@ -262,15 +273,7 @@ int falco_init(int argc, char **argv) // Some combinations of arguments are not allowed. if (daemon && pidfilename == "") { - falco_logger::log(LOG_ERR, "If -d is provided, a pid file must also be provided. Exiting.\n"); - result = EXIT_FAILURE; - goto exit; - } - - if (daemon && output_name == "stdout") { - falco_logger::log(LOG_ERR, "If -d is provided, can not output to stdout. Exiting.\n"); - result = EXIT_FAILURE; - goto exit; + throw sinsp_exception("If -d is provided, a pid file must also be provided"); } ifstream* conf_stream; @@ -279,9 +282,7 @@ int falco_init(int argc, char **argv) conf_stream = new ifstream(conf_filename); if (!conf_stream->good()) { - falco_logger::log(LOG_ERR, "Could not find configuration file at " + conf_filename + ". Exiting.\n"); - result = EXIT_FAILURE; - goto exit; + throw sinsp_exception("Could not find configuration file at " + conf_filename); } } else @@ -308,13 +309,13 @@ int falco_init(int argc, char **argv) falco_configuration config; if (conf_filename.size()) { - config.init(conf_filename); + config.init(conf_filename, cmdline_options); // log after config init because config determines where logs go falco_logger::log(LOG_INFO, "Falco initialized with configuration file " + conf_filename + "\n"); } else { - config.init(); + config.init(cmdline_options); falco_logger::log(LOG_INFO, "Falco initialized. No configuration file found, proceeding with defaults\n"); } @@ -441,10 +442,13 @@ int falco_init(int argc, char **argv) goto exit; } - // Close stdin, stdout, stderr. + // Close stdin, stdout, stderr and reopen to /dev/null close(0); close(1); close(2); + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDWR); + open("/dev/null", O_RDWR); } do_inspect(inspector, @@ -455,13 +459,13 @@ int falco_init(int argc, char **argv) } catch(sinsp_exception& e) { - falco_logger::log(LOG_ERR, "Runtime error: " + string(e.what()) + ". Exiting.\n"); + display_fatal_err("Runtime error: " + string(e.what()) + ". Exiting.\n", daemon); result = EXIT_FAILURE; } catch(...) { - falco_logger::log(LOG_ERR, "Unexpected error, Exiting\n"); + display_fatal_err("Unexpected error, Exiting\n", daemon); result = EXIT_FAILURE; }