From 9f1d123ec406fbffa6d9c2eb9e3c85223cf647df Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 6 Sep 2024 10:27:56 +0200 Subject: [PATCH 1/9] new(userspace): added json schema validation for rules. Also, a new `--rule-schema` cli option was added to print the schema and leave. Signed-off-by: Federico Di Pierro --- userspace/falco/CMakeLists.txt | 1 + userspace/falco/app/actions/actions.h | 1 + userspace/falco/app/actions/helpers.h | 16 +++- userspace/falco/app/actions/load_config.cpp | 2 +- .../falco/app/actions/load_rules_files.cpp | 10 ++- .../falco/app/actions/print_rule_schema.cpp | 31 +++++++ .../app/actions/validate_rules_files.cpp | 9 +- userspace/falco/app/app.cpp | 1 + userspace/falco/app/options.cpp | 1 + userspace/falco/app/options.h | 1 + userspace/falco/configuration.cpp | 86 ++++++++++++++++++- userspace/falco/configuration.h | 2 +- 12 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 userspace/falco/app/actions/print_rule_schema.cpp diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index c5f8b366575..0758b245fef 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -50,6 +50,7 @@ add_library(falco_application STATIC app/actions/create_requested_paths.cpp app/actions/close_inspectors.cpp app/actions/print_config_schema.cpp + app/actions/print_rule_schema.cpp configuration.cpp falco_outputs.cpp outputs_file.cpp diff --git a/userspace/falco/app/actions/actions.h b/userspace/falco/app/actions/actions.h index 564bffba5f3..8882d33b5a2 100644 --- a/userspace/falco/app/actions/actions.h +++ b/userspace/falco/app/actions/actions.h @@ -45,6 +45,7 @@ falco::app::run_result print_ignored_events(const falco::app::state& s); falco::app::run_result print_kernel_version(const falco::app::state& s); falco::app::run_result print_page_size(const falco::app::state& s); falco::app::run_result print_plugin_info(const falco::app::state& s); +falco::app::run_result print_rule_schema(falco::app::state& s); falco::app::run_result print_support(falco::app::state& s); falco::app::run_result print_syscall_events(falco::app::state& s); falco::app::run_result print_version(falco::app::state& s); diff --git a/userspace/falco/app/actions/helpers.h b/userspace/falco/app/actions/helpers.h index 69562bf23dd..222480b11e3 100644 --- a/userspace/falco/app/actions/helpers.h +++ b/userspace/falco/app/actions/helpers.h @@ -26,6 +26,9 @@ namespace falco { namespace app { namespace actions { +// Map that holds { rule filename | validation status } for each rule file read. +typedef std::map rule_read_res; + bool check_rules_plugin_requirements(falco::app::state& s, std::string& err); void print_enabled_event_sources(falco::app::state& s); void activate_interesting_kernel_tracepoints(falco::app::state& s, std::unique_ptr& inspector); @@ -40,10 +43,14 @@ falco::app::run_result open_live_inspector( const std::string& source); template -void read_files(InputIterator begin, InputIterator end, +rule_read_res read_files(InputIterator begin, InputIterator end, std::vector& rules_contents, - falco::load_result::rules_contents_t& rc) + falco::load_result::rules_contents_t& rc, + const nlohmann::json& schema={}) { + rule_read_res res; + yaml_helper reader; + std::string validation; // Read the contents in a first pass for(auto it = begin; it != end; it++) { @@ -57,6 +64,9 @@ void read_files(InputIterator begin, InputIterator end, std::string rules_content((std::istreambuf_iterator(is)), std::istreambuf_iterator()); + + reader.load_from_string(rules_content, schema, &validation); + res[filename] = validation; rules_contents.emplace_back(std::move(rules_content)); } @@ -75,6 +85,8 @@ void read_files(InputIterator begin, InputIterator end, { throw falco_exception("Unexpected mismatch in rules content name/rules content sets?"); } + + return res; } diff --git a/userspace/falco/app/actions/load_config.cpp b/userspace/falco/app/actions/load_config.cpp index 2a00eefd78a..03954ff1740 100644 --- a/userspace/falco/app/actions/load_config.cpp +++ b/userspace/falco/app/actions/load_config.cpp @@ -66,7 +66,7 @@ falco::app::run_result falco::app::actions::load_config(const falco::app::state& auto config_path = pair.first; auto validation = pair.second; auto priority = validation == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; - falco_logger::log(priority, std::string(" ") + config_path + " | validation: " + validation + "\n"); + falco_logger::log(priority, std::string(" ") + config_path + " | schema validation: " + validation + "\n"); } } diff --git a/userspace/falco/app/actions/load_rules_files.cpp b/userspace/falco/app/actions/load_rules_files.cpp index d1b6df45c83..d1cb2726bce 100644 --- a/userspace/falco/app/actions/load_rules_files.cpp +++ b/userspace/falco/app/actions/load_rules_files.cpp @@ -54,11 +54,12 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& std::vector rules_contents; falco::load_result::rules_contents_t rc; + rule_read_res validation_res; try { - read_files(s.config->m_loaded_rules_filenames.begin(), + validation_res = read_files(s.config->m_loaded_rules_filenames.begin(), s.config->m_loaded_rules_filenames.end(), rules_contents, - rc); + rc, s.config->m_rule_schema); } catch(falco_exception& e) { @@ -66,9 +67,12 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& } std::string err = ""; + falco_logger::log(falco_logger::level::INFO, "Loading rules from:\n"); for(auto &filename : s.config->m_loaded_rules_filenames) { - falco_logger::log(falco_logger::level::INFO, "Loading rules from file " + filename + "\n"); + auto validation = validation_res[filename]; + auto priority = validation == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; + falco_logger::log(priority, std::string(" ") + filename + " | schema validation: " + validation + "\n"); std::unique_ptr res; res = s.engine->load_rules(rc.at(filename), filename); diff --git a/userspace/falco/app/actions/print_rule_schema.cpp b/userspace/falco/app/actions/print_rule_schema.cpp new file mode 100644 index 00000000000..b153ec8cd1e --- /dev/null +++ b/userspace/falco/app/actions/print_rule_schema.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 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 "actions.h" + +using namespace falco::app; +using namespace falco::app::actions; + +falco::app::run_result falco::app::actions::print_rule_schema(falco::app::state &s) +{ + if(s.options.print_rule_schema) + { + printf("%s", s.config->m_rule_schema.dump(2).c_str()); + return run_result::exit(); + } + return run_result::ok(); +} diff --git a/userspace/falco/app/actions/validate_rules_files.cpp b/userspace/falco/app/actions/validate_rules_files.cpp index 7edeb0423e7..3ecf0d423bc 100644 --- a/userspace/falco/app/actions/validate_rules_files.cpp +++ b/userspace/falco/app/actions/validate_rules_files.cpp @@ -33,11 +33,12 @@ falco::app::run_result falco::app::actions::validate_rules_files(falco::app::sta std::vector rules_contents; falco::load_result::rules_contents_t rc; + rule_read_res validation_res; try { - read_files(s.options.validate_rules_filenames.begin(), + validation_res = read_files(s.options.validate_rules_filenames.begin(), s.options.validate_rules_filenames.end(), rules_contents, - rc); + rc,s.config->m_rule_schema); } catch(falco_exception& e) { @@ -71,7 +72,9 @@ falco::app::run_result falco::app::actions::validate_rules_files(falco::app::sta falco_logger::log(falco_logger::level::INFO, "Validating rules file(s):\n"); for(const auto& file : s.options.validate_rules_filenames) { - falco_logger::log(falco_logger::level::INFO, " " + file + "\n"); + auto validation = validation_res[file]; + auto priority = validation == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; + falco_logger::log(priority, std::string(" ") + file + " | schema validation: " + validation + "\n"); } // The json output encompasses all files so the diff --git a/userspace/falco/app/app.cpp b/userspace/falco/app/app.cpp index 4d1dfae344e..fb92f43405a 100644 --- a/userspace/falco/app/app.cpp +++ b/userspace/falco/app/app.cpp @@ -61,6 +61,7 @@ bool falco::app::run(falco::app::state& s, bool& restart, std::string& errstr) // loading plugins, opening inspector, etc.). std::list run_steps = { falco::app::actions::print_config_schema, + falco::app::actions::print_rule_schema, falco::app::actions::load_config, falco::app::actions::print_help, falco::app::actions::print_kernel_version, diff --git a/userspace/falco/app/options.cpp b/userspace/falco/app/options.cpp index 52439b3b609..e7658736502 100644 --- a/userspace/falco/app/options.cpp +++ b/userspace/falco/app/options.cpp @@ -115,6 +115,7 @@ void options::define(cxxopts::Options& opts) ("c", "Configuration file. If not specified tries " FALCO_SOURCE_CONF_FILE ", " FALCO_INSTALL_CONF_FILE ".", cxxopts::value(conf_filename), "") #endif ("config-schema", "Print the config json schema and exit.", cxxopts::value(print_config_schema)->default_value("false")) + ("rule-schema", "Print the rule json schema and exit.", cxxopts::value(print_rule_schema)->default_value("false")) ("A", "Monitor all events supported by Falco and defined in rules and configs. Some events are ignored by default when -A is not specified (the -i option lists these events ignored). Using -A can impact performance. This option has no effect when reproducing events from a capture file.", cxxopts::value(all_events)->default_value("false")) ("b,print-base64", "Print data buffers in base64. This is useful for encoding binary data that needs to be used over media designed to consume this format.") #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) && !defined(MINIMAL_BUILD) diff --git a/userspace/falco/app/options.h b/userspace/falco/app/options.h index 5e8d0c33848..6dc70de55ad 100644 --- a/userspace/falco/app/options.h +++ b/userspace/falco/app/options.h @@ -41,6 +41,7 @@ class options { // Each of these maps directly to a command line option. bool help = false; bool print_config_schema = false; + bool print_rule_schema = false; std::string conf_filename; bool all_events = false; sinsp_evt::param_fmt event_buffer_format = sinsp_evt::PF_NORMAL; diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index b936b81cbc2..4bab01ad38d 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -53,7 +53,88 @@ static re2::RE2 ip_address_re("((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]| // https://learn.microsoft.com/en-us/cpp/cpp/string-and-character-literals-cpp?view=msvc-170#size-of-string-literals // Just use any available online tool, eg: https://jsonformatter.org/json-minify // to format the json, add the new fields, and then minify it again. -static const std::string schema_json_string = R"({"$schema":"http://json-schema.org/draft-06/schema#","$ref":"#/definitions/FalcoConfig","definitions":{"FalcoConfig":{"type":"object","additionalProperties":false,"properties":{"append_output":{"type":"array","items":{"$ref":"#/definitions/AppendOutput"}},"config_files":{"type":"array","items":{"type":"string"}},"watch_config_files":{"type":"boolean"},"rules_files":{"type":"array","items":{"type":"string"}},"rule_files":{"type":"array","items":{"type":"string"}},"rules":{"type":"array","items":{"$ref":"#/definitions/Rule"}},"engine":{"$ref":"#/definitions/Engine"},"load_plugins":{"type":"array","items":{"type":"string"}},"plugins":{"type":"array","items":{"$ref":"#/definitions/Plugin"}},"time_format_iso_8601":{"type":"boolean"},"priority":{"type":"string"},"json_output":{"type":"boolean"},"json_include_output_property":{"type":"boolean"},"json_include_tags_property":{"type":"boolean"},"buffered_outputs":{"type":"boolean"},"rule_matching":{"type":"string"},"outputs_queue":{"$ref":"#/definitions/OutputsQueue"},"stdout_output":{"$ref":"#/definitions/Output"},"syslog_output":{"$ref":"#/definitions/Output"},"file_output":{"$ref":"#/definitions/FileOutput"},"http_output":{"$ref":"#/definitions/HTTPOutput"},"program_output":{"$ref":"#/definitions/ProgramOutput"},"grpc_output":{"$ref":"#/definitions/Output"},"grpc":{"$ref":"#/definitions/Grpc"},"webserver":{"$ref":"#/definitions/Webserver"},"log_stderr":{"type":"boolean"},"log_syslog":{"type":"boolean"},"log_level":{"type":"string"},"libs_logger":{"$ref":"#/definitions/LibsLogger"},"output_timeout":{"type":"integer"},"syscall_event_timeouts":{"$ref":"#/definitions/SyscallEventTimeouts"},"syscall_event_drops":{"$ref":"#/definitions/SyscallEventDrops"},"metrics":{"$ref":"#/definitions/Metrics"},"base_syscalls":{"$ref":"#/definitions/BaseSyscalls"},"falco_libs":{"$ref":"#/definitions/FalcoLibs"},"container_engines":{"type":"object","additionalProperties":false,"properties":{"docker":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"cri":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"sockets":{"type":"array","items":{"type":"string"}},"disable_async":{"type":"boolean"}}},"podman":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"lxc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"libvirt_lxc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"bpm":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}}}}},"title":"FalcoConfig"},"AppendOutput":{"type":"object","additionalProperties":false,"properties":{"source":{"type":"string"},"tag":{"type":"string"},"rule":{"type":"string"},"format":{"type":"string"},"fields":{"type":"array","items":{"anyOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"string"}]}}}},"BaseSyscalls":{"type":"object","additionalProperties":false,"properties":{"custom_set":{"type":"array","items":{"type":"string"}},"repair":{"type":"boolean"}},"minProperties":1,"title":"BaseSyscalls"},"Engine":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string"},"kmod":{"$ref":"#/definitions/Kmod"},"ebpf":{"$ref":"#/definitions/Ebpf"},"modern_ebpf":{"$ref":"#/definitions/ModernEbpf"},"replay":{"$ref":"#/definitions/Replay"},"gvisor":{"$ref":"#/definitions/Gvisor"}},"required":["kind"],"title":"Engine"},"Ebpf":{"type":"object","additionalProperties":false,"properties":{"probe":{"type":"string"},"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"required":["probe"],"title":"Ebpf"},"Gvisor":{"type":"object","additionalProperties":false,"properties":{"config":{"type":"string"},"root":{"type":"string"}},"required":["config","root"],"title":"Gvisor"},"Kmod":{"type":"object","additionalProperties":false,"properties":{"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"minProperties":1,"title":"Kmod"},"ModernEbpf":{"type":"object","additionalProperties":false,"properties":{"cpus_for_each_buffer":{"type":"integer"},"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"title":"ModernEbpf"},"Replay":{"type":"object","additionalProperties":false,"properties":{"capture_file":{"type":"string"}},"required":["capture_file"],"title":"Replay"},"FalcoLibs":{"type":"object","additionalProperties":false,"properties":{"thread_table_size":{"type":"integer"}},"minProperties":1,"title":"FalcoLibs"},"FileOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"keep_alive":{"type":"boolean"},"filename":{"type":"string"}},"minProperties":1,"title":"FileOutput"},"Grpc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"bind_address":{"type":"string"},"threadiness":{"type":"integer"}},"minProperties":1,"title":"Grpc"},"Output":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"minProperties":1,"title":"Output"},"HTTPOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"url":{"type":"string","format":"uri","qt-uri-protocols":["http"]},"user_agent":{"type":"string"},"insecure":{"type":"boolean"},"ca_cert":{"type":"string"},"ca_bundle":{"type":"string"},"ca_path":{"type":"string"},"mtls":{"type":"boolean"},"client_cert":{"type":"string"},"client_key":{"type":"string"},"echo":{"type":"boolean"},"compress_uploads":{"type":"boolean"},"keep_alive":{"type":"boolean"}},"minProperties":1,"title":"HTTPOutput"},"LibsLogger":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"severity":{"type":"string"}},"minProperties":1,"title":"LibsLogger"},"Metrics":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"interval":{"type":"string"},"output_rule":{"type":"boolean"},"output_file":{"type":"string"},"rules_counters_enabled":{"type":"boolean"},"resource_utilization_enabled":{"type":"boolean"},"state_counters_enabled":{"type":"boolean"},"kernel_event_counters_enabled":{"type":"boolean"},"libbpf_stats_enabled":{"type":"boolean"},"plugins_metrics_enabled":{"type":"boolean"},"convert_memory_to_mb":{"type":"boolean"},"include_empty_values":{"type":"boolean"}},"minProperties":1,"title":"Metrics"},"OutputsQueue":{"type":"object","additionalProperties":false,"properties":{"capacity":{"type":"integer"}},"minProperties":1,"title":"OutputsQueue"},"Plugin":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"library_path":{"type":"string"},"init_config":{"type":"string"},"open_params":{"type":"string"}},"required":["library_path","name"],"title":"Plugin"},"ProgramOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"keep_alive":{"type":"boolean"},"program":{"type":"string"}},"required":["program"],"title":"ProgramOutput"},"Rule":{"type":"object","additionalProperties":false,"properties":{"disable":{"$ref":"#/definitions/Able"},"enable":{"$ref":"#/definitions/Able"}},"minProperties":1,"title":"Rule"},"Able":{"type":"object","additionalProperties":false,"properties":{"rule":{"type":"string"},"tag":{"type":"string"}},"minProperties":1,"title":"Able"},"SyscallEventDrops":{"type":"object","additionalProperties":false,"properties":{"threshold":{"type":"number"},"actions":{"type":"array","items":{"type":"string"}},"rate":{"type":"number"},"max_burst":{"type":"integer"},"simulate_drops":{"type":"boolean"}},"minProperties":1,"title":"SyscallEventDrops"},"SyscallEventTimeouts":{"type":"object","additionalProperties":false,"properties":{"max_consecutives":{"type":"integer"}},"minProperties":1,"title":"SyscallEventTimeouts"},"Webserver":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"threadiness":{"type":"integer"},"listen_port":{"type":"integer"},"listen_address":{"type":"string"},"k8s_healthz_endpoint":{"type":"string"},"prometheus_metrics_enabled":{"type":"boolean"},"ssl_enabled":{"type":"boolean"},"ssl_certificate":{"type":"string"}},"minProperties":1,"title":"Webserver"}}})"; +static const std::string config_schema_string = R"({"$schema":"http://json-schema.org/draft-06/schema#","$ref":"#/definitions/FalcoConfig","definitions":{"FalcoConfig":{"type":"object","additionalProperties":false,"properties":{"append_output":{"type":"array","items":{"$ref":"#/definitions/AppendOutput"}},"config_files":{"type":"array","items":{"type":"string"}},"watch_config_files":{"type":"boolean"},"rules_files":{"type":"array","items":{"type":"string"}},"rule_files":{"type":"array","items":{"type":"string"}},"rules":{"type":"array","items":{"$ref":"#/definitions/Rule"}},"engine":{"$ref":"#/definitions/Engine"},"load_plugins":{"type":"array","items":{"type":"string"}},"plugins":{"type":"array","items":{"$ref":"#/definitions/Plugin"}},"time_format_iso_8601":{"type":"boolean"},"priority":{"type":"string"},"json_output":{"type":"boolean"},"json_include_output_property":{"type":"boolean"},"json_include_tags_property":{"type":"boolean"},"buffered_outputs":{"type":"boolean"},"rule_matching":{"type":"string"},"outputs_queue":{"$ref":"#/definitions/OutputsQueue"},"stdout_output":{"$ref":"#/definitions/Output"},"syslog_output":{"$ref":"#/definitions/Output"},"file_output":{"$ref":"#/definitions/FileOutput"},"http_output":{"$ref":"#/definitions/HTTPOutput"},"program_output":{"$ref":"#/definitions/ProgramOutput"},"grpc_output":{"$ref":"#/definitions/Output"},"grpc":{"$ref":"#/definitions/Grpc"},"webserver":{"$ref":"#/definitions/Webserver"},"log_stderr":{"type":"boolean"},"log_syslog":{"type":"boolean"},"log_level":{"type":"string"},"libs_logger":{"$ref":"#/definitions/LibsLogger"},"output_timeout":{"type":"integer"},"syscall_event_timeouts":{"$ref":"#/definitions/SyscallEventTimeouts"},"syscall_event_drops":{"$ref":"#/definitions/SyscallEventDrops"},"metrics":{"$ref":"#/definitions/Metrics"},"base_syscalls":{"$ref":"#/definitions/BaseSyscalls"},"falco_libs":{"$ref":"#/definitions/FalcoLibs"},"container_engines":{"type":"object","additionalProperties":false,"properties":{"docker":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"cri":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"sockets":{"type":"array","items":{"type":"string"}},"disable_async":{"type":"boolean"}}},"podman":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"lxc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"libvirt_lxc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"bpm":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}}}}},"title":"FalcoConfig"},"AppendOutput":{"type":"object","additionalProperties":false,"properties":{"source":{"type":"string"},"tag":{"type":"string"},"rule":{"type":"string"},"format":{"type":"string"},"fields":{"type":"array","items":{"anyOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"string"}]}}}},"BaseSyscalls":{"type":"object","additionalProperties":false,"properties":{"custom_set":{"type":"array","items":{"type":"string"}},"repair":{"type":"boolean"}},"minProperties":1,"title":"BaseSyscalls"},"Engine":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string"},"kmod":{"$ref":"#/definitions/Kmod"},"ebpf":{"$ref":"#/definitions/Ebpf"},"modern_ebpf":{"$ref":"#/definitions/ModernEbpf"},"replay":{"$ref":"#/definitions/Replay"},"gvisor":{"$ref":"#/definitions/Gvisor"}},"required":["kind"],"title":"Engine"},"Ebpf":{"type":"object","additionalProperties":false,"properties":{"probe":{"type":"string"},"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"required":["probe"],"title":"Ebpf"},"Gvisor":{"type":"object","additionalProperties":false,"properties":{"config":{"type":"string"},"root":{"type":"string"}},"required":["config","root"],"title":"Gvisor"},"Kmod":{"type":"object","additionalProperties":false,"properties":{"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"minProperties":1,"title":"Kmod"},"ModernEbpf":{"type":"object","additionalProperties":false,"properties":{"cpus_for_each_buffer":{"type":"integer"},"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"title":"ModernEbpf"},"Replay":{"type":"object","additionalProperties":false,"properties":{"capture_file":{"type":"string"}},"required":["capture_file"],"title":"Replay"},"FalcoLibs":{"type":"object","additionalProperties":false,"properties":{"thread_table_size":{"type":"integer"}},"minProperties":1,"title":"FalcoLibs"},"FileOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"keep_alive":{"type":"boolean"},"filename":{"type":"string"}},"minProperties":1,"title":"FileOutput"},"Grpc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"bind_address":{"type":"string"},"threadiness":{"type":"integer"}},"minProperties":1,"title":"Grpc"},"Output":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"minProperties":1,"title":"Output"},"HTTPOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"url":{"type":"string","format":"uri","qt-uri-protocols":["http"]},"user_agent":{"type":"string"},"insecure":{"type":"boolean"},"ca_cert":{"type":"string"},"ca_bundle":{"type":"string"},"ca_path":{"type":"string"},"mtls":{"type":"boolean"},"client_cert":{"type":"string"},"client_key":{"type":"string"},"echo":{"type":"boolean"},"compress_uploads":{"type":"boolean"},"keep_alive":{"type":"boolean"}},"minProperties":1,"title":"HTTPOutput"},"LibsLogger":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"severity":{"type":"string"}},"minProperties":1,"title":"LibsLogger"},"Metrics":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"interval":{"type":"string"},"output_rule":{"type":"boolean"},"output_file":{"type":"string"},"rules_counters_enabled":{"type":"boolean"},"resource_utilization_enabled":{"type":"boolean"},"state_counters_enabled":{"type":"boolean"},"kernel_event_counters_enabled":{"type":"boolean"},"libbpf_stats_enabled":{"type":"boolean"},"plugins_metrics_enabled":{"type":"boolean"},"convert_memory_to_mb":{"type":"boolean"},"include_empty_values":{"type":"boolean"}},"minProperties":1,"title":"Metrics"},"OutputsQueue":{"type":"object","additionalProperties":false,"properties":{"capacity":{"type":"integer"}},"minProperties":1,"title":"OutputsQueue"},"Plugin":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"library_path":{"type":"string"},"init_config":{"type":"string"},"open_params":{"type":"string"}},"required":["library_path","name"],"title":"Plugin"},"ProgramOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"keep_alive":{"type":"boolean"},"program":{"type":"string"}},"required":["program"],"title":"ProgramOutput"},"Rule":{"type":"object","additionalProperties":false,"properties":{"disable":{"$ref":"#/definitions/Able"},"enable":{"$ref":"#/definitions/Able"}},"minProperties":1,"title":"Rule"},"Able":{"type":"object","additionalProperties":false,"properties":{"rule":{"type":"string"},"tag":{"type":"string"}},"minProperties":1,"title":"Able"},"SyscallEventDrops":{"type":"object","additionalProperties":false,"properties":{"threshold":{"type":"number"},"actions":{"type":"array","items":{"type":"string"}},"rate":{"type":"number"},"max_burst":{"type":"integer"},"simulate_drops":{"type":"boolean"}},"minProperties":1,"title":"SyscallEventDrops"},"SyscallEventTimeouts":{"type":"object","additionalProperties":false,"properties":{"max_consecutives":{"type":"integer"}},"minProperties":1,"title":"SyscallEventTimeouts"},"Webserver":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"threadiness":{"type":"integer"},"listen_port":{"type":"integer"},"listen_address":{"type":"string"},"k8s_healthz_endpoint":{"type":"string"},"prometheus_metrics_enabled":{"type":"boolean"},"ssl_enabled":{"type":"boolean"},"ssl_certificate":{"type":"string"}},"minProperties":1,"title":"Webserver"}}})"; + +static const std::string rule_schema_string = R"( +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "array", + "items": { + "$ref": "#/definitions/FalcoRule" + }, + "definitions": { + "FalcoRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "required_engine_version": { + "type": "string" + }, + "macro": { + "type": "string" + }, + "condition": { + "type": "string" + }, + "list": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Item" + } + }, + "rule": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "output": { + "type": "string" + }, + "priority": { + "$ref": "#/definitions/Priority" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [], + "title": "FalcoRule" + }, + "Item": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "title": "Item" + }, + "Priority": { + "type": "string", + "enum": [ + "WARNING", + "NOTICE", + "INFO", + "ERROR", + "CRITICAL" + ], + "title": "Priority" + } + } +} +)"; falco_configuration::falco_configuration(): m_json_output(false), @@ -87,7 +168,8 @@ falco_configuration::falco_configuration(): m_container_engines_disable_cri_async(false), m_container_engines_cri_socket_paths({"/run/containerd/containerd.sock", "/run/crio/crio.sock","/run/k3s/containerd/containerd.sock"}) { - m_config_schema = nlohmann::json::parse(schema_json_string); + m_config_schema = nlohmann::json::parse(config_schema_string); + m_rule_schema = nlohmann::json::parse(rule_schema_string); } config_loaded_res falco_configuration::init_from_content(const std::string& config_content, const std::vector& cmdline_options, const std::string& filename) diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 7a3281a6c68..2be1f23395e 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -210,9 +210,9 @@ class falco_configuration replay_config m_replay = {}; gvisor_config m_gvisor = {}; - // Needed by tests yaml_helper m_config; nlohmann::json m_config_schema; + nlohmann::json m_rule_schema; private: void merge_config_files(const std::string& config_name, config_loaded_res &res); From 0054a6f35f4b7626c9cdf0449503c81b863d101c Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 6 Sep 2024 14:52:11 +0200 Subject: [PATCH 2/9] cleanup(userspace,unit_tests): moved rule schema under engine. Also, moved yaml_helper under engine/ folder. Ported rule json schema validation in the engine. Also, updated rule_loader tests to check for validation. Signed-off-by: Federico Di Pierro --- unit_tests/engine/test_rule_loader.cpp | 80 ++++++++-- userspace/engine/falco_engine.cpp | 151 +++++++++++++++++- userspace/engine/falco_engine.h | 2 + userspace/engine/falco_load_result.h | 3 + userspace/engine/rule_loader.cpp | 14 +- userspace/engine/rule_loader.h | 4 + userspace/engine/rule_loader_reader.cpp | 9 +- userspace/engine/rule_loader_reader.h | 2 +- userspace/{falco => engine}/yaml_helper.h | 38 ++++- userspace/falco/app/actions/helpers.h | 15 +- .../falco/app/actions/load_rules_files.cpp | 10 +- .../falco/app/actions/print_rule_schema.cpp | 2 +- .../app/actions/validate_rules_files.cpp | 18 +-- userspace/falco/configuration.cpp | 82 ---------- userspace/falco/configuration.h | 1 - 15 files changed, 295 insertions(+), 136 deletions(-) rename userspace/{falco => engine}/yaml_helper.h (93%) diff --git a/unit_tests/engine/test_rule_loader.cpp b/unit_tests/engine/test_rule_loader.cpp index 6d76e830afe..ae680cd6cd7 100644 --- a/unit_tests/engine/test_rule_loader.cpp +++ b/unit_tests/engine/test_rule_loader.cpp @@ -1,6 +1,9 @@ #include #include "../test_falco_engine.h" +#include "yaml_helper.h" + +#define ASSERT_VALIDATION_STATUS(status) ASSERT_TRUE(sinsp_utils::startswith(m_load_result->schema_validation(), status)) std::string s_sample_ruleset = "sample-ruleset"; std::string s_sample_source = falco_common::syscall_source; @@ -24,6 +27,7 @@ TEST_F(test_falco_engine, list_append) )END"; ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string; + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("legit_rule"),"(evt.type = open and proc.name in (ash, bash, csh, ksh, sh, tcsh, zsh, dash, pwsh))"); } @@ -48,6 +52,7 @@ TEST_F(test_falco_engine, condition_append) )END"; ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string; + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("legit_rule"),"(evt.type = open and (((proc.aname = sshd and proc.name != sshd) or proc.name = systemd-logind or proc.name = login) or proc.name = ssh))"); } @@ -72,6 +77,7 @@ TEST_F(test_falco_engine, rule_override_append) std::string rule_name = "legit_rule"; ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string; + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); // Here we don't use the deprecated `append` flag, so we don't expect the warning. ASSERT_FALSE(check_warning_message(WARNING_APPEND)); @@ -102,6 +108,7 @@ TEST_F(test_falco_engine, rule_append) )END"; ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string; + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); // We should have at least one warning because the 'append' flag is deprecated. ASSERT_TRUE(check_warning_message(WARNING_APPEND)); @@ -128,6 +135,7 @@ TEST_F(test_falco_engine, rule_override_replace) std::string rule_name = "legit_rule"; ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string; + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); auto rule_description = m_engine->describe_rule(&rule_name, {}); ASSERT_EQ(rule_description["rules"][0]["info"]["condition"].template get(), @@ -161,6 +169,7 @@ TEST_F(test_falco_engine, rule_override_append_replace) std::string rule_name = "legit_rule"; ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string; + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); auto rule_description = m_engine->describe_rule(&rule_name, {}); ASSERT_EQ(rule_description["rules"][0]["info"]["condition"].template get(), @@ -196,6 +205,7 @@ TEST_F(test_falco_engine, rule_incorrect_override_type) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message("Key 'priority' cannot be appended to, use 'replace' instead")); ASSERT_TRUE(std::string(m_load_result_json["errors"][0]["context"]["snippet"]).find("priority: append") != std::string::npos); } @@ -219,6 +229,7 @@ TEST_F(test_falco_engine, rule_incorrect_append_override) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); // We should have at least one warning because the 'append' flag is deprecated. ASSERT_TRUE(check_warning_message(WARNING_APPEND)); @@ -248,6 +259,7 @@ TEST_F(test_falco_engine, macro_override_append_before_macro_definition) // We cannot define a macro override before the macro definition. ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_MACRO)); } @@ -273,6 +285,7 @@ TEST_F(test_falco_engine, macro_override_replace_before_macro_definition) // The first override defines a macro that is overridden by the second macro definition ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"evt.type in (open, openat)"); } @@ -297,6 +310,7 @@ TEST_F(test_falco_engine, macro_append_before_macro_definition) // We cannot define a macro override before the macro definition. ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_MACRO)); } @@ -322,6 +336,7 @@ TEST_F(test_falco_engine, macro_override_append_after_macro_definition) // We cannot define a macro override before the macro definition. ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type in (open, openat) or evt.type = openat2)"); } @@ -346,6 +361,7 @@ TEST_F(test_falco_engine, macro_append_after_macro_definition) // We cannot define a macro override before the macro definition. ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type in (open, openat) or evt.type = openat2)"); } @@ -366,6 +382,7 @@ TEST_F(test_falco_engine, rule_override_append_before_rule_definition) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_RULE_APPEND)); } @@ -386,6 +403,7 @@ TEST_F(test_falco_engine, rule_override_replace_before_rule_definition) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_RULE_REPLACE)); } @@ -405,6 +423,7 @@ TEST_F(test_falco_engine, rule_append_before_rule_definition) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_RULE_APPEND)); } @@ -424,6 +443,7 @@ TEST_F(test_falco_engine, rule_override_append_after_rule_definition) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type in (open, openat) and proc.name = cat)"); } @@ -442,6 +462,7 @@ TEST_F(test_falco_engine, rule_append_after_rule_definition) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type in (open, openat) and proc.name = cat)"); } @@ -470,6 +491,7 @@ TEST_F(test_falco_engine, list_override_append_wrong_key) // considered. so in this situation, we are defining the list 2 times. The // second one overrides the first one. ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_failed) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type = execve and proc.name in (blkid))"); } @@ -494,6 +516,7 @@ TEST_F(test_falco_engine, list_override_append_before_list_definition) // We cannot define a list override before the list definition. ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_LIST)); } @@ -518,6 +541,7 @@ TEST_F(test_falco_engine, list_override_replace_before_list_definition) // With override replace we define a first list that then is overridden by the second one. ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type = execve and proc.name in (blkid))"); } @@ -541,6 +565,7 @@ TEST_F(test_falco_engine, list_append_before_list_definition) // We cannot define a list append before the list definition. ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message(ERROR_NO_PREVIOUS_LIST)); } @@ -564,6 +589,7 @@ TEST_F(test_falco_engine, list_override_append_after_list_definition) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type = execve and proc.name in (blkid, csi-provisioner, csi-attacher))"); } @@ -585,6 +611,7 @@ TEST_F(test_falco_engine, list_append_after_list_definition) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"(evt.type = execve and proc.name in (blkid, csi-provisioner, csi-attacher))"); } @@ -605,6 +632,7 @@ TEST_F(test_falco_engine, rule_override_without_field) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message("An append override for 'condition' was specified but 'condition' is not defined")); } @@ -627,6 +655,7 @@ TEST_F(test_falco_engine, rule_override_extra_field) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message("Unexpected key 'priority'")); } @@ -651,6 +680,7 @@ TEST_F(test_falco_engine, missing_enabled_key_with_override) // In the rule override we miss `enabled: true` ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message("'enabled' was specified but 'enabled' is not defined")); } @@ -675,6 +705,7 @@ TEST_F(test_falco_engine, rule_override_with_enabled) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); // The rule should be enabled at the end. EXPECT_EQ(num_rules_for_ruleset(), 1); @@ -712,6 +743,7 @@ TEST_F(test_falco_engine, rule_override_exceptions_required_fields) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); ASSERT_TRUE(check_error_message("Item has no mapping for key 'fields'")) << m_load_result_json.dump(); } @@ -728,6 +760,7 @@ TEST_F(test_falco_engine, rule_not_enabled) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); EXPECT_EQ(num_rules_for_ruleset(), 0); } @@ -747,6 +780,7 @@ TEST_F(test_falco_engine, rule_enabled_warning) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_warning_message(WARNING_ENABLED)); // The rule should be enabled at the end. EXPECT_EQ(num_rules_for_ruleset(), 1); @@ -772,6 +806,7 @@ TEST_F(test_falco_engine, rule_enabled_is_ignored_by_append) // 'enabled' is ignored by the append, this syntax is not supported // so the rule is not enabled. ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(num_rules_for_ruleset(), 0); } @@ -797,6 +832,7 @@ TEST_F(test_falco_engine, rewrite_rule) // The above syntax is not supported, we cannot override the content // of a rule in this way. ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); // In this case the rule is completely overridden but this syntax is not supported. EXPECT_EQ(num_rules_for_ruleset(), 1); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"proc.name = cat"); @@ -817,6 +853,7 @@ TEST_F(test_falco_engine, required_engine_version_semver) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); } @@ -835,6 +872,7 @@ TEST_F(test_falco_engine, required_engine_version_not_semver) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); } @@ -853,6 +891,7 @@ TEST_F(test_falco_engine, required_engine_version_invalid) )END"; ASSERT_FALSE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_error_message("Unable to parse engine version")); } @@ -865,22 +904,23 @@ TEST_F(test_falco_engine, list_value_with_escaping) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(m_load_result->successful()); - ASSERT_TRUE(m_load_result->has_warnings()); // a warning for the unused list + ASSERT_TRUE(m_load_result->has_warnings()); // a warning for the unused list - auto rule_description = m_engine->describe_rule(nullptr, {}); - ASSERT_TRUE(m_load_result->successful()); - ASSERT_EQ(rule_description["rules"].size(), 0); - ASSERT_EQ(rule_description["macros"].size(), 0); - ASSERT_EQ(rule_description["lists"].size(), 1); + auto rule_description = m_engine->describe_rule(nullptr, {}); + ASSERT_TRUE(m_load_result->successful()); + ASSERT_EQ(rule_description["rules"].size(), 0); + ASSERT_EQ(rule_description["macros"].size(), 0); + ASSERT_EQ(rule_description["lists"].size(), 1); - // escaped values must not be interpreted as list refs by mistake - ASSERT_EQ(rule_description["lists"][0]["details"]["lists"].size(), 0); + // escaped values must not be interpreted as list refs by mistake + ASSERT_EQ(rule_description["lists"][0]["details"]["lists"].size(), 0); - // values should be escaped correctly - ASSERT_EQ(rule_description["lists"][0]["details"]["items_compiled"].size(), 2); - ASSERT_EQ(rule_description["lists"][0]["details"]["items_compiled"][0].template get(), "non_escaped_val"); - ASSERT_EQ(rule_description["lists"][0]["details"]["items_compiled"][1].template get(), "escaped val"); + // values should be escaped correctly + ASSERT_EQ(rule_description["lists"][0]["details"]["items_compiled"].size(), 2); + ASSERT_EQ(rule_description["lists"][0]["details"]["items_compiled"][0].template get(), "non_escaped_val"); + ASSERT_EQ(rule_description["lists"][0]["details"]["items_compiled"][1].template get(), "escaped val"); } TEST_F(test_falco_engine, exceptions_condition) @@ -900,6 +940,7 @@ TEST_F(test_falco_engine, exceptions_condition) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_EQ(get_compiled_rule_condition("test_rule"),"((proc.cmdline contains curl or proc.cmdline contains wget) and not proc.cmdline contains \"curl 127.0.0.1\")"); } @@ -911,6 +952,7 @@ TEST_F(test_falco_engine, macro_name_invalid) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_warning_message("Macro has an invalid name. Macro names should match a regular expression")); } @@ -930,6 +972,7 @@ TEST_F(test_falco_engine, list_name_invalid) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_warning_message("List has an invalid name. List names should match a regular expression")); } @@ -958,6 +1001,7 @@ TEST_F(test_falco_engine, exceptions_append_no_values) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_failed) << m_load_result->schema_validation(); ASSERT_TRUE(check_warning_message("Overriding/appending exception with no values")); } @@ -985,6 +1029,7 @@ TEST_F(test_falco_engine, exceptions_override_no_values) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_failed) << m_load_result->schema_validation(); ASSERT_TRUE(check_warning_message("Overriding/appending exception with no values")); } @@ -1010,6 +1055,7 @@ TEST_F(test_falco_engine, exceptions_names_not_unique) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_TRUE(check_warning_message("Multiple definitions of exception")); } @@ -1033,6 +1079,7 @@ TEST_F(test_falco_engine, exceptions_values_rhs_field_ambiguous) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = proc.pname)"); EXPECT_TRUE(check_warning_message("'proc.pname' may be a valid field misused as a const string value")); } @@ -1049,6 +1096,7 @@ TEST_F(test_falco_engine, exceptions_values_rhs_field_ambiguous_quoted) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = proc.pname)"); EXPECT_TRUE(check_warning_message("'proc.pname' may be a valid field misused as a const string value")); } @@ -1065,6 +1113,7 @@ TEST_F(test_falco_engine, exceptions_values_rhs_field_ambiguous_space_quoted) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = \"proc.pname \")"); EXPECT_TRUE(check_warning_message("'proc.pname ' may be a valid field misused as a const string value")); } @@ -1081,6 +1130,7 @@ TEST_F(test_falco_engine, exceptions_values_rhs_transformer) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = toupper(proc.pname))"); } @@ -1096,6 +1146,7 @@ TEST_F(test_falco_engine, exceptions_values_transformer_value_quoted) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = toupper(proc.pname))"); } @@ -1111,6 +1162,7 @@ TEST_F(test_falco_engine, exceptions_values_transformer_space) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = \"toupper( proc.pname)\")"); EXPECT_TRUE(check_warning_message("'toupper( proc.pname)' may be a valid field transformer misused as a const string value")); } @@ -1127,6 +1179,7 @@ TEST_F(test_falco_engine, exceptions_values_transformer_space_quoted) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not proc.name = \"toupper( proc.pname)\")"); EXPECT_TRUE(check_warning_message("'toupper( proc.pname)' may be a valid field transformer misused as a const string value")); } @@ -1143,6 +1196,7 @@ TEST_F(test_falco_engine, exceptions_fields_transformer) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); EXPECT_FALSE(has_warnings()); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not tolower(proc.name) = test)"); } @@ -1159,6 +1213,7 @@ TEST_F(test_falco_engine, exceptions_fields_transformer_quoted) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not tolower(proc.name) = test)"); } @@ -1175,6 +1230,7 @@ TEST_F(test_falco_engine, exceptions_fields_transformer_space_quoted) )END"; ASSERT_TRUE(load_rules(rules_content, "rules.yaml")); + ASSERT_VALIDATION_STATUS(yaml_helper::validation_ok) << m_load_result->schema_validation(); ASSERT_FALSE(has_warnings()); EXPECT_EQ(get_compiled_rule_condition("test_rule"), "(evt.type = open and not tolower(proc.name) = test)"); } diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index dd2603bf632..a02aa17e5b5 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -47,6 +47,153 @@ limitations under the License. const std::string falco_engine::s_default_ruleset = "falco-default-ruleset"; +static const std::string rule_schema_string = R"( +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "array", + "items": { + "$ref": "#/definitions/FalcoRule" + }, + "definitions": { + "FalcoRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "required_engine_version": { + "type": "string" + }, + "macro": { + "type": "string" + }, + "condition": { + "type": "string" + }, + "list": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Item" + } + }, + "rule": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "output": { + "type": "string" + }, + "append": { + "type": "boolean" + }, + "priority": { + "$ref": "#/definitions/Priority" + }, + "exceptions": { + "type": "array", + "items": { + "$ref": "#/definitions/Exception" + } + }, + "override": { + "$ref": "#/definitions/Override" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [], + "title": "FalcoRule" + }, + "Item": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "title": "Item" + }, + "Exception": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "fields": {}, + "comps": {}, + "values": {} + }, + "required": [ + "name", + "values" + ], + "title": "Exception" + }, + "Priority": { + "type": "string", + "enum": [ + "WARNING", + "NOTICE", + "INFO", + "ERROR", + "CRITICAL" + ], + "title": "Priority" + }, + "OverriddenItem": { + "type": "string", + "enum": [ + "append", + "replace" + ], + "title": "Priority" + }, + "Override": { + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "$ref": "#/definitions/OverriddenItem" + }, + "desc": { + "$ref": "#/definitions/OverriddenItem" + }, + "condition": { + "$ref": "#/definitions/OverriddenItem" + }, + "output": { + "$ref": "#/definitions/OverriddenItem" + }, + "priority": { + "$ref": "#/definitions/OverriddenItem" + }, + "enabled": { + "$ref": "#/definitions/OverriddenItem" + }, + "exceptions": { + "$ref": "#/definitions/OverriddenItem" + } + }, + "minProperties":1, + "title": "Override" + } + } +} +)"; + using namespace falco; falco_engine::falco_engine(bool seed_rng) @@ -67,6 +214,8 @@ falco_engine::falco_engine(bool seed_rng) m_default_ruleset_id = find_ruleset_id(s_default_ruleset); fill_engine_state_funcs(m_engine_state); + + m_rule_schema = nlohmann::json::parse(rule_schema_string); } falco_engine::~falco_engine() @@ -198,7 +347,7 @@ std::unique_ptr falco_engine::load_rules(const std::string &rules_c cfg.extra_output_fields = m_extra_output_fields; // read rules YAML file and collect its definitions - if(m_rule_reader->read(cfg, *m_rule_collector)) + if(m_rule_reader->read(cfg, *m_rule_collector, m_rule_schema)) { // compile the definitions (resolve macro/list refs, exceptions, ...) m_last_compile_output = m_rule_compiler->new_compile_output(); diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index 5af6d1c96b8..17171c4c436 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -355,6 +355,8 @@ class falco_engine const std::vector& plugins, std::string& err) const; + nlohmann::json m_rule_schema; + private: // Create a ruleset using the provided factory and set the // engine state funcs for it. diff --git a/userspace/engine/falco_load_result.h b/userspace/engine/falco_load_result.h index 1f7b261bb84..23f2f8fcd7b 100644 --- a/userspace/engine/falco_load_result.h +++ b/userspace/engine/falco_load_result.h @@ -87,6 +87,9 @@ class load_result { // has_warnings() can both be true if there were only warnings. virtual bool has_warnings() = 0; + // Return json schema validation status. + virtual std::string schema_validation() = 0; + // This represents a set of rules contents as a mapping from // rules content name (usually filename) to rules content. The // rules content is actually a reference to the actual string diff --git a/userspace/engine/rule_loader.cpp b/userspace/engine/rule_loader.cpp index cd00a19b05d..b843419e4bb 100644 --- a/userspace/engine/rule_loader.cpp +++ b/userspace/engine/rule_loader.cpp @@ -18,6 +18,7 @@ limitations under the License. #include #include "rule_loader.h" +#include "yaml_helper.h" static const std::string item_type_strings[] = { @@ -282,7 +283,8 @@ std::string rule_loader::context::snippet(const falco::load_result::rules_conten rule_loader::result::result(const std::string &name) : name(name), - success(true) + success(true), + schema_validation_str(yaml_helper::validation_none) { } @@ -296,6 +298,11 @@ bool rule_loader::result::has_warnings() return (warnings.size() > 0); } +std::string rule_loader::result::schema_validation() +{ + return schema_validation_str; +} + void rule_loader::result::add_error(load_result::error_code ec, const std::string& msg, const context& ctx) { error err = {ec, msg, ctx}; @@ -311,6 +318,11 @@ void rule_loader::result::add_warning(load_result::warning_code wc, const std::s warnings.push_back(warn); } +void rule_loader::result::set_schema_validation_status(const std::string& status) +{ + schema_validation_str = status; +} + const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents) { if(verbose) diff --git a/userspace/engine/rule_loader.h b/userspace/engine/rule_loader.h index 3ec15f903a9..2cbbca6add8 100644 --- a/userspace/engine/rule_loader.h +++ b/userspace/engine/rule_loader.h @@ -247,12 +247,16 @@ namespace rule_loader void add_warning(falco::load_result::warning_code ec, const std::string& msg, const context& ctx); + + void set_schema_validation_status(const std::string& status); + std::string schema_validation(); protected: const std::string& as_summary_string(); const std::string& as_verbose_string(const falco::load_result::rules_contents_t& contents); std::string name; bool success; + std::string schema_validation_str; std::vector errors; std::vector warnings; diff --git a/userspace/engine/rule_loader_reader.cpp b/userspace/engine/rule_loader_reader.cpp index 4d875cb440d..6148a21ac88 100644 --- a/userspace/engine/rule_loader_reader.cpp +++ b/userspace/engine/rule_loader_reader.cpp @@ -23,6 +23,7 @@ limitations under the License. #include "rule_loader_reader.h" #include "falco_engine_version.h" #include "rule_loading_messages.h" +#include "yaml_helper.h" #include #include @@ -783,13 +784,15 @@ void rule_loader::reader::read_item( } } -bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& collector) +bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& collector, const nlohmann::json& schema) { std::vector docs; + yaml_helper reader; + std::string schema_validation; rule_loader::context ctx(cfg.name); try { - docs = YAML::LoadAll(cfg.content); + docs = reader.loadall_from_string(cfg.content, schema, &schema_validation); } catch (YAML::ParserException& e) { @@ -807,7 +810,7 @@ bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& colle cfg.res->add_error(falco::load_result::LOAD_ERR_YAML_PARSE, "unknown YAML parsing error", ctx); return false; } - + cfg.res->set_schema_validation_status(schema_validation); for (auto doc = docs.begin(); doc != docs.end(); doc++) { if (doc->IsDefined() && !doc->IsNull()) diff --git a/userspace/engine/rule_loader_reader.h b/userspace/engine/rule_loader_reader.h index 0c331234443..d105d0372e3 100644 --- a/userspace/engine/rule_loader_reader.h +++ b/userspace/engine/rule_loader_reader.h @@ -43,7 +43,7 @@ class reader \brief Reads the contents of a ruleset and uses a collector to store thew new definitions */ - virtual bool read(configuration& cfg, collector& loader); + virtual bool read(configuration& cfg, collector& loader, const nlohmann::json& schema={}); /*! \brief Engine version used to be represented as a simple progressive diff --git a/userspace/falco/yaml_helper.h b/userspace/engine/yaml_helper.h similarity index 93% rename from userspace/falco/yaml_helper.h rename to userspace/engine/yaml_helper.h index d1134873fa6..067f006fdc9 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/engine/yaml_helper.h @@ -41,10 +41,10 @@ limitations under the License. #include #include -#include "config_falco.h" +//#include "config_falco.h" -#include "event_drops.h" -#include "falco_outputs.h" +//#include "event_drops.h" +//#include "falco_outputs.h" class yaml_helper; @@ -89,7 +89,37 @@ class yaml_helper inline static const std::string configs_key = "config_files"; inline static const std::string validation_ok = "ok"; inline static const std::string validation_failed = "failed"; - inline static const std::string validation_none = "schema not provided"; + inline static const std::string validation_none = "none"; + + /** + * Load all the YAML document represented by the input string. + * Since this is used by rule loader, does not process env vars. + */ + std::vector loadall_from_string(const std::string& input, const nlohmann::json& schema={}, std::string *validation=nullptr) + { + auto nodes = YAML::LoadAll(input); + if (validation) + { + if(!schema.empty()) + { + // Validate each node. + for (const auto& node : nodes) + { + *validation = validate_node(node, schema); + if (*validation != validation_ok) + { + // Return first error + break; + } + } + } + else + { + *validation = validation_none; + } + } + return nodes; + } /** * Load the YAML document represented by the input string. diff --git a/userspace/falco/app/actions/helpers.h b/userspace/falco/app/actions/helpers.h index 222480b11e3..4bd62ffc823 100644 --- a/userspace/falco/app/actions/helpers.h +++ b/userspace/falco/app/actions/helpers.h @@ -26,9 +26,6 @@ namespace falco { namespace app { namespace actions { -// Map that holds { rule filename | validation status } for each rule file read. -typedef std::map rule_read_res; - bool check_rules_plugin_requirements(falco::app::state& s, std::string& err); void print_enabled_event_sources(falco::app::state& s); void activate_interesting_kernel_tracepoints(falco::app::state& s, std::unique_ptr& inspector); @@ -43,14 +40,10 @@ falco::app::run_result open_live_inspector( const std::string& source); template -rule_read_res read_files(InputIterator begin, InputIterator end, +void read_files(InputIterator begin, InputIterator end, std::vector& rules_contents, - falco::load_result::rules_contents_t& rc, - const nlohmann::json& schema={}) + falco::load_result::rules_contents_t& rc) { - rule_read_res res; - yaml_helper reader; - std::string validation; // Read the contents in a first pass for(auto it = begin; it != end; it++) { @@ -65,8 +58,6 @@ rule_read_res read_files(InputIterator begin, InputIterator end, std::string rules_content((std::istreambuf_iterator(is)), std::istreambuf_iterator()); - reader.load_from_string(rules_content, schema, &validation); - res[filename] = validation; rules_contents.emplace_back(std::move(rules_content)); } @@ -85,8 +76,6 @@ rule_read_res read_files(InputIterator begin, InputIterator end, { throw falco_exception("Unexpected mismatch in rules content name/rules content sets?"); } - - return res; } diff --git a/userspace/falco/app/actions/load_rules_files.cpp b/userspace/falco/app/actions/load_rules_files.cpp index d1cb2726bce..ec4050baaf7 100644 --- a/userspace/falco/app/actions/load_rules_files.cpp +++ b/userspace/falco/app/actions/load_rules_files.cpp @@ -54,12 +54,11 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& std::vector rules_contents; falco::load_result::rules_contents_t rc; - rule_read_res validation_res; try { - validation_res = read_files(s.config->m_loaded_rules_filenames.begin(), + read_files(s.config->m_loaded_rules_filenames.begin(), s.config->m_loaded_rules_filenames.end(), rules_contents, - rc, s.config->m_rule_schema); + rc); } catch(falco_exception& e) { @@ -70,12 +69,11 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& falco_logger::log(falco_logger::level::INFO, "Loading rules from:\n"); for(auto &filename : s.config->m_loaded_rules_filenames) { - auto validation = validation_res[filename]; - auto priority = validation == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; - falco_logger::log(priority, std::string(" ") + filename + " | schema validation: " + validation + "\n"); std::unique_ptr res; res = s.engine->load_rules(rc.at(filename), filename); + auto priority = res->schema_validation() == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; + falco_logger::log(priority, std::string(" ") + filename + " | schema validation: " + res->schema_validation() + "\n"); if(!res->successful()) { diff --git a/userspace/falco/app/actions/print_rule_schema.cpp b/userspace/falco/app/actions/print_rule_schema.cpp index b153ec8cd1e..1052c597d54 100644 --- a/userspace/falco/app/actions/print_rule_schema.cpp +++ b/userspace/falco/app/actions/print_rule_schema.cpp @@ -24,7 +24,7 @@ falco::app::run_result falco::app::actions::print_rule_schema(falco::app::state { if(s.options.print_rule_schema) { - printf("%s", s.config->m_rule_schema.dump(2).c_str()); + printf("%s", s.engine->m_rule_schema.dump(2).c_str()); return run_result::exit(); } return run_result::ok(); diff --git a/userspace/falco/app/actions/validate_rules_files.cpp b/userspace/falco/app/actions/validate_rules_files.cpp index 3ecf0d423bc..0ab57272b98 100644 --- a/userspace/falco/app/actions/validate_rules_files.cpp +++ b/userspace/falco/app/actions/validate_rules_files.cpp @@ -33,12 +33,11 @@ falco::app::run_result falco::app::actions::validate_rules_files(falco::app::sta std::vector rules_contents; falco::load_result::rules_contents_t rc; - rule_read_res validation_res; try { - validation_res = read_files(s.options.validate_rules_filenames.begin(), + read_files(s.options.validate_rules_filenames.begin(), s.options.validate_rules_filenames.end(), rules_contents, - rc,s.config->m_rule_schema); + rc); } catch(falco_exception& e) { @@ -69,24 +68,21 @@ falco::app::run_result falco::app::actions::validate_rules_files(falco::app::sta // printed when verbose is true. std::string summary; - falco_logger::log(falco_logger::level::INFO, "Validating rules file(s):\n"); - for(const auto& file : s.options.validate_rules_filenames) - { - auto validation = validation_res[file]; - auto priority = validation == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; - falco_logger::log(priority, std::string(" ") + file + " | schema validation: " + validation + "\n"); - } - // The json output encompasses all files so the // validation result is a single json object. std::string err = ""; nlohmann::json results = nlohmann::json::array(); + falco_logger::log(falco_logger::level::INFO, "Validating rules file(s):\n"); for(auto &filename : s.options.validate_rules_filenames) { std::unique_ptr res; res = s.engine->load_rules(rc.at(filename), filename); + + auto priority = res->schema_validation() == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; + falco_logger::log(priority, std::string(" ") + filename + " | schema validation: " + res->schema_validation() + "\n"); + if (!check_rules_plugin_requirements(s, err)) { return run_result::fatal(err); diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 4bab01ad38d..0e9a49cea42 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -55,87 +55,6 @@ static re2::RE2 ip_address_re("((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]| // to format the json, add the new fields, and then minify it again. static const std::string config_schema_string = R"({"$schema":"http://json-schema.org/draft-06/schema#","$ref":"#/definitions/FalcoConfig","definitions":{"FalcoConfig":{"type":"object","additionalProperties":false,"properties":{"append_output":{"type":"array","items":{"$ref":"#/definitions/AppendOutput"}},"config_files":{"type":"array","items":{"type":"string"}},"watch_config_files":{"type":"boolean"},"rules_files":{"type":"array","items":{"type":"string"}},"rule_files":{"type":"array","items":{"type":"string"}},"rules":{"type":"array","items":{"$ref":"#/definitions/Rule"}},"engine":{"$ref":"#/definitions/Engine"},"load_plugins":{"type":"array","items":{"type":"string"}},"plugins":{"type":"array","items":{"$ref":"#/definitions/Plugin"}},"time_format_iso_8601":{"type":"boolean"},"priority":{"type":"string"},"json_output":{"type":"boolean"},"json_include_output_property":{"type":"boolean"},"json_include_tags_property":{"type":"boolean"},"buffered_outputs":{"type":"boolean"},"rule_matching":{"type":"string"},"outputs_queue":{"$ref":"#/definitions/OutputsQueue"},"stdout_output":{"$ref":"#/definitions/Output"},"syslog_output":{"$ref":"#/definitions/Output"},"file_output":{"$ref":"#/definitions/FileOutput"},"http_output":{"$ref":"#/definitions/HTTPOutput"},"program_output":{"$ref":"#/definitions/ProgramOutput"},"grpc_output":{"$ref":"#/definitions/Output"},"grpc":{"$ref":"#/definitions/Grpc"},"webserver":{"$ref":"#/definitions/Webserver"},"log_stderr":{"type":"boolean"},"log_syslog":{"type":"boolean"},"log_level":{"type":"string"},"libs_logger":{"$ref":"#/definitions/LibsLogger"},"output_timeout":{"type":"integer"},"syscall_event_timeouts":{"$ref":"#/definitions/SyscallEventTimeouts"},"syscall_event_drops":{"$ref":"#/definitions/SyscallEventDrops"},"metrics":{"$ref":"#/definitions/Metrics"},"base_syscalls":{"$ref":"#/definitions/BaseSyscalls"},"falco_libs":{"$ref":"#/definitions/FalcoLibs"},"container_engines":{"type":"object","additionalProperties":false,"properties":{"docker":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"cri":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"sockets":{"type":"array","items":{"type":"string"}},"disable_async":{"type":"boolean"}}},"podman":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"lxc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"libvirt_lxc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}},"bpm":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}}}}}},"title":"FalcoConfig"},"AppendOutput":{"type":"object","additionalProperties":false,"properties":{"source":{"type":"string"},"tag":{"type":"string"},"rule":{"type":"string"},"format":{"type":"string"},"fields":{"type":"array","items":{"anyOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"string"}]}}}},"BaseSyscalls":{"type":"object","additionalProperties":false,"properties":{"custom_set":{"type":"array","items":{"type":"string"}},"repair":{"type":"boolean"}},"minProperties":1,"title":"BaseSyscalls"},"Engine":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string"},"kmod":{"$ref":"#/definitions/Kmod"},"ebpf":{"$ref":"#/definitions/Ebpf"},"modern_ebpf":{"$ref":"#/definitions/ModernEbpf"},"replay":{"$ref":"#/definitions/Replay"},"gvisor":{"$ref":"#/definitions/Gvisor"}},"required":["kind"],"title":"Engine"},"Ebpf":{"type":"object","additionalProperties":false,"properties":{"probe":{"type":"string"},"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"required":["probe"],"title":"Ebpf"},"Gvisor":{"type":"object","additionalProperties":false,"properties":{"config":{"type":"string"},"root":{"type":"string"}},"required":["config","root"],"title":"Gvisor"},"Kmod":{"type":"object","additionalProperties":false,"properties":{"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"minProperties":1,"title":"Kmod"},"ModernEbpf":{"type":"object","additionalProperties":false,"properties":{"cpus_for_each_buffer":{"type":"integer"},"buf_size_preset":{"type":"integer"},"drop_failed_exit":{"type":"boolean"}},"title":"ModernEbpf"},"Replay":{"type":"object","additionalProperties":false,"properties":{"capture_file":{"type":"string"}},"required":["capture_file"],"title":"Replay"},"FalcoLibs":{"type":"object","additionalProperties":false,"properties":{"thread_table_size":{"type":"integer"}},"minProperties":1,"title":"FalcoLibs"},"FileOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"keep_alive":{"type":"boolean"},"filename":{"type":"string"}},"minProperties":1,"title":"FileOutput"},"Grpc":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"bind_address":{"type":"string"},"threadiness":{"type":"integer"}},"minProperties":1,"title":"Grpc"},"Output":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"minProperties":1,"title":"Output"},"HTTPOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"url":{"type":"string","format":"uri","qt-uri-protocols":["http"]},"user_agent":{"type":"string"},"insecure":{"type":"boolean"},"ca_cert":{"type":"string"},"ca_bundle":{"type":"string"},"ca_path":{"type":"string"},"mtls":{"type":"boolean"},"client_cert":{"type":"string"},"client_key":{"type":"string"},"echo":{"type":"boolean"},"compress_uploads":{"type":"boolean"},"keep_alive":{"type":"boolean"}},"minProperties":1,"title":"HTTPOutput"},"LibsLogger":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"severity":{"type":"string"}},"minProperties":1,"title":"LibsLogger"},"Metrics":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"interval":{"type":"string"},"output_rule":{"type":"boolean"},"output_file":{"type":"string"},"rules_counters_enabled":{"type":"boolean"},"resource_utilization_enabled":{"type":"boolean"},"state_counters_enabled":{"type":"boolean"},"kernel_event_counters_enabled":{"type":"boolean"},"libbpf_stats_enabled":{"type":"boolean"},"plugins_metrics_enabled":{"type":"boolean"},"convert_memory_to_mb":{"type":"boolean"},"include_empty_values":{"type":"boolean"}},"minProperties":1,"title":"Metrics"},"OutputsQueue":{"type":"object","additionalProperties":false,"properties":{"capacity":{"type":"integer"}},"minProperties":1,"title":"OutputsQueue"},"Plugin":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"library_path":{"type":"string"},"init_config":{"type":"string"},"open_params":{"type":"string"}},"required":["library_path","name"],"title":"Plugin"},"ProgramOutput":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"keep_alive":{"type":"boolean"},"program":{"type":"string"}},"required":["program"],"title":"ProgramOutput"},"Rule":{"type":"object","additionalProperties":false,"properties":{"disable":{"$ref":"#/definitions/Able"},"enable":{"$ref":"#/definitions/Able"}},"minProperties":1,"title":"Rule"},"Able":{"type":"object","additionalProperties":false,"properties":{"rule":{"type":"string"},"tag":{"type":"string"}},"minProperties":1,"title":"Able"},"SyscallEventDrops":{"type":"object","additionalProperties":false,"properties":{"threshold":{"type":"number"},"actions":{"type":"array","items":{"type":"string"}},"rate":{"type":"number"},"max_burst":{"type":"integer"},"simulate_drops":{"type":"boolean"}},"minProperties":1,"title":"SyscallEventDrops"},"SyscallEventTimeouts":{"type":"object","additionalProperties":false,"properties":{"max_consecutives":{"type":"integer"}},"minProperties":1,"title":"SyscallEventTimeouts"},"Webserver":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"threadiness":{"type":"integer"},"listen_port":{"type":"integer"},"listen_address":{"type":"string"},"k8s_healthz_endpoint":{"type":"string"},"prometheus_metrics_enabled":{"type":"boolean"},"ssl_enabled":{"type":"boolean"},"ssl_certificate":{"type":"string"}},"minProperties":1,"title":"Webserver"}}})"; -static const std::string rule_schema_string = R"( -{ - "$schema": "http://json-schema.org/draft-06/schema#", - "type": "array", - "items": { - "$ref": "#/definitions/FalcoRule" - }, - "definitions": { - "FalcoRule": { - "type": "object", - "additionalProperties": false, - "properties": { - "required_engine_version": { - "type": "string" - }, - "macro": { - "type": "string" - }, - "condition": { - "type": "string" - }, - "list": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/Item" - } - }, - "rule": { - "type": "string" - }, - "desc": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "output": { - "type": "string" - }, - "priority": { - "$ref": "#/definitions/Priority" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [], - "title": "FalcoRule" - }, - "Item": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Item" - }, - "Priority": { - "type": "string", - "enum": [ - "WARNING", - "NOTICE", - "INFO", - "ERROR", - "CRITICAL" - ], - "title": "Priority" - } - } -} -)"; - falco_configuration::falco_configuration(): m_json_output(false), m_json_include_output_property(true), @@ -169,7 +88,6 @@ falco_configuration::falco_configuration(): m_container_engines_cri_socket_paths({"/run/containerd/containerd.sock", "/run/crio/crio.sock","/run/k3s/containerd/containerd.sock"}) { m_config_schema = nlohmann::json::parse(config_schema_string); - m_rule_schema = nlohmann::json::parse(rule_schema_string); } config_loaded_res falco_configuration::init_from_content(const std::string& config_content, const std::vector& cmdline_options, const std::string& filename) diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 2be1f23395e..33d0addd6ab 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -212,7 +212,6 @@ class falco_configuration yaml_helper m_config; nlohmann::json m_config_schema; - nlohmann::json m_rule_schema; private: void merge_config_files(const std::string& config_name, config_loaded_res &res); From 6156d3c609b7c55ef3efde9d710268bb043c1966 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 6 Sep 2024 14:54:15 +0200 Subject: [PATCH 3/9] chore(userspace): minified rule schema json. Signed-off-by: Federico Di Pierro --- userspace/engine/falco_engine.cpp | 147 +----------------------------- 1 file changed, 1 insertion(+), 146 deletions(-) diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index a02aa17e5b5..b48dad9cb84 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -47,152 +47,7 @@ limitations under the License. const std::string falco_engine::s_default_ruleset = "falco-default-ruleset"; -static const std::string rule_schema_string = R"( -{ - "$schema": "http://json-schema.org/draft-06/schema#", - "type": "array", - "items": { - "$ref": "#/definitions/FalcoRule" - }, - "definitions": { - "FalcoRule": { - "type": "object", - "additionalProperties": false, - "properties": { - "required_engine_version": { - "type": "string" - }, - "macro": { - "type": "string" - }, - "condition": { - "type": "string" - }, - "list": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/Item" - } - }, - "rule": { - "type": "string" - }, - "desc": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "output": { - "type": "string" - }, - "append": { - "type": "boolean" - }, - "priority": { - "$ref": "#/definitions/Priority" - }, - "exceptions": { - "type": "array", - "items": { - "$ref": "#/definitions/Exception" - } - }, - "override": { - "$ref": "#/definitions/Override" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [], - "title": "FalcoRule" - }, - "Item": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "title": "Item" - }, - "Exception": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "fields": {}, - "comps": {}, - "values": {} - }, - "required": [ - "name", - "values" - ], - "title": "Exception" - }, - "Priority": { - "type": "string", - "enum": [ - "WARNING", - "NOTICE", - "INFO", - "ERROR", - "CRITICAL" - ], - "title": "Priority" - }, - "OverriddenItem": { - "type": "string", - "enum": [ - "append", - "replace" - ], - "title": "Priority" - }, - "Override": { - "type": "object", - "additionalProperties": false, - "properties": { - "items": { - "$ref": "#/definitions/OverriddenItem" - }, - "desc": { - "$ref": "#/definitions/OverriddenItem" - }, - "condition": { - "$ref": "#/definitions/OverriddenItem" - }, - "output": { - "$ref": "#/definitions/OverriddenItem" - }, - "priority": { - "$ref": "#/definitions/OverriddenItem" - }, - "enabled": { - "$ref": "#/definitions/OverriddenItem" - }, - "exceptions": { - "$ref": "#/definitions/OverriddenItem" - } - }, - "minProperties":1, - "title": "Override" - } - } -} -)"; +static const std::string rule_schema_string = R"({"$schema":"http://json-schema.org/draft-06/schema#","type":"array","items":{"$ref":"#/definitions/FalcoRule"},"definitions":{"FalcoRule":{"type":"object","additionalProperties":false,"properties":{"required_engine_version":{"type":"string"},"macro":{"type":"string"},"condition":{"type":"string"},"list":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/definitions/Item"}},"rule":{"type":"string"},"desc":{"type":"string"},"enabled":{"type":"boolean"},"output":{"type":"string"},"append":{"type":"boolean"},"priority":{"$ref":"#/definitions/Priority"},"exceptions":{"type":"array","items":{"$ref":"#/definitions/Exception"}},"override":{"$ref":"#/definitions/Override"},"tags":{"type":"array","items":{"type":"string"}}},"required":[],"title":"FalcoRule"},"Item":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Item"},"Exception":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"fields":{},"comps":{},"values":{}},"required":["name","values"],"title":"Exception"},"Priority":{"type":"string","enum":["WARNING","NOTICE","INFO","ERROR","CRITICAL"],"title":"Priority"},"OverriddenItem":{"type":"string","enum":["append","replace"],"title":"Priority"},"Override":{"type":"object","additionalProperties":false,"properties":{"items":{"$ref":"#/definitions/OverriddenItem"},"desc":{"$ref":"#/definitions/OverriddenItem"},"condition":{"$ref":"#/definitions/OverriddenItem"},"output":{"$ref":"#/definitions/OverriddenItem"},"priority":{"$ref":"#/definitions/OverriddenItem"},"enabled":{"$ref":"#/definitions/OverriddenItem"},"exceptions":{"$ref":"#/definitions/OverriddenItem"}},"minProperties":1,"title":"Override"}}})"; using namespace falco; From 03210854dd9a61b61aefe3d8000ae70d4ee9d801 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 6 Sep 2024 15:00:21 +0200 Subject: [PATCH 4/9] cleanup(userspace): drop unused includes from yaml_helper. Signed-off-by: Federico Di Pierro --- userspace/engine/yaml_helper.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/userspace/engine/yaml_helper.h b/userspace/engine/yaml_helper.h index 067f006fdc9..07ad8960b40 100644 --- a/userspace/engine/yaml_helper.h +++ b/userspace/engine/yaml_helper.h @@ -41,11 +41,6 @@ limitations under the License. #include #include -//#include "config_falco.h" - -//#include "event_drops.h" -//#include "falco_outputs.h" - class yaml_helper; class yaml_visitor { From 9ab6e35b247a3d7d2f7f0fd61681a9b5a6971e31 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Fri, 6 Sep 2024 15:19:10 +0200 Subject: [PATCH 5/9] chore(userspace/falco): reverted file to master version. Signed-off-by: Federico Di Pierro --- userspace/falco/app/actions/helpers.h | 1 - 1 file changed, 1 deletion(-) diff --git a/userspace/falco/app/actions/helpers.h b/userspace/falco/app/actions/helpers.h index 4bd62ffc823..69562bf23dd 100644 --- a/userspace/falco/app/actions/helpers.h +++ b/userspace/falco/app/actions/helpers.h @@ -57,7 +57,6 @@ void read_files(InputIterator begin, InputIterator end, std::string rules_content((std::istreambuf_iterator(is)), std::istreambuf_iterator()); - rules_contents.emplace_back(std::move(rules_content)); } From e4139760be74640766a08e1f191a265777324412 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 9 Sep 2024 10:10:54 +0200 Subject: [PATCH 6/9] update(userspace/engine): fixed priorities in rules schema. Signed-off-by: Federico Di Pierro Co-authored-by: Leonardo Grasso --- userspace/engine/falco_engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index b48dad9cb84..f7f79268988 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -47,7 +47,7 @@ limitations under the License. const std::string falco_engine::s_default_ruleset = "falco-default-ruleset"; -static const std::string rule_schema_string = R"({"$schema":"http://json-schema.org/draft-06/schema#","type":"array","items":{"$ref":"#/definitions/FalcoRule"},"definitions":{"FalcoRule":{"type":"object","additionalProperties":false,"properties":{"required_engine_version":{"type":"string"},"macro":{"type":"string"},"condition":{"type":"string"},"list":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/definitions/Item"}},"rule":{"type":"string"},"desc":{"type":"string"},"enabled":{"type":"boolean"},"output":{"type":"string"},"append":{"type":"boolean"},"priority":{"$ref":"#/definitions/Priority"},"exceptions":{"type":"array","items":{"$ref":"#/definitions/Exception"}},"override":{"$ref":"#/definitions/Override"},"tags":{"type":"array","items":{"type":"string"}}},"required":[],"title":"FalcoRule"},"Item":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Item"},"Exception":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"fields":{},"comps":{},"values":{}},"required":["name","values"],"title":"Exception"},"Priority":{"type":"string","enum":["WARNING","NOTICE","INFO","ERROR","CRITICAL"],"title":"Priority"},"OverriddenItem":{"type":"string","enum":["append","replace"],"title":"Priority"},"Override":{"type":"object","additionalProperties":false,"properties":{"items":{"$ref":"#/definitions/OverriddenItem"},"desc":{"$ref":"#/definitions/OverriddenItem"},"condition":{"$ref":"#/definitions/OverriddenItem"},"output":{"$ref":"#/definitions/OverriddenItem"},"priority":{"$ref":"#/definitions/OverriddenItem"},"enabled":{"$ref":"#/definitions/OverriddenItem"},"exceptions":{"$ref":"#/definitions/OverriddenItem"}},"minProperties":1,"title":"Override"}}})"; +static const std::string rule_schema_string = R"({"$schema":"http://json-schema.org/draft-06/schema#","type":"array","items":{"$ref":"#/definitions/FalcoRule"},"definitions":{"FalcoRule":{"type":"object","additionalProperties":false,"properties":{"required_engine_version":{"type":"string"},"macro":{"type":"string"},"condition":{"type":"string"},"list":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/definitions/Item"}},"rule":{"type":"string"},"desc":{"type":"string"},"enabled":{"type":"boolean"},"output":{"type":"string"},"append":{"type":"boolean"},"priority":{"$ref":"#/definitions/Priority"},"exceptions":{"type":"array","items":{"$ref":"#/definitions/Exception"}},"override":{"$ref":"#/definitions/Override"},"tags":{"type":"array","items":{"type":"string"}}},"required":[],"title":"FalcoRule"},"Item":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Item"},"Exception":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"fields":{},"comps":{},"values":{}},"required":["name","values"],"title":"Exception"},"Priority":{"type":"string","enum":["EMERGENCY","ALERT","CRITICAL","ERROR","WARNING","NOTICE","INFO","INFORMATIONAL","DEBUG"],"title":"Priority"},"OverriddenItem":{"type":"string","enum":["append","replace"],"title":"Priority"},"Override":{"type":"object","additionalProperties":false,"properties":{"items":{"$ref":"#/definitions/OverriddenItem"},"desc":{"$ref":"#/definitions/OverriddenItem"},"condition":{"$ref":"#/definitions/OverriddenItem"},"output":{"$ref":"#/definitions/OverriddenItem"},"priority":{"$ref":"#/definitions/OverriddenItem"},"enabled":{"$ref":"#/definitions/OverriddenItem"},"exceptions":{"$ref":"#/definitions/OverriddenItem"}},"minProperties":1,"title":"Override"}}})"; using namespace falco; From 482ea97842236f76ae1d93030a170e8d871391a7 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Mon, 9 Sep 2024 14:24:42 +0200 Subject: [PATCH 7/9] chore(userspace): added schema validation info to `rule_loader::result` `as_json` and `as_string` outputs. Signed-off-by: Federico Di Pierro --- userspace/engine/rule_loader.cpp | 26 +++++++++++++++++-- .../app/actions/validate_rules_files.cpp | 11 ++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/userspace/engine/rule_loader.cpp b/userspace/engine/rule_loader.cpp index b843419e4bb..01e8c089507 100644 --- a/userspace/engine/rule_loader.cpp +++ b/userspace/engine/rule_loader.cpp @@ -363,6 +363,12 @@ const std::string& rule_loader::result::as_summary_string() os << "Invalid"; } + // Only print schema validation info if any validation was requested + if (schema_validation_str != yaml_helper::validation_none) + { + os << " | schema validation: " << schema_validation_str; + } + if(!errors.empty()) { os << std::endl; @@ -435,6 +441,12 @@ const std::string& rule_loader::result::as_verbose_string(const rules_contents_t os << "Invalid"; } + // Only print schema validation info if any validation was requested + if (schema_validation_str != yaml_helper::validation_none) + { + os << " | schema validation: " << schema_validation_str; + } + if (!errors.empty()) { os << std::endl; @@ -494,8 +506,19 @@ const nlohmann::json& rule_loader::result::as_json(const rules_contents_t& conte j["name"] = name; j["successful"] = success; - j["errors"] = nlohmann::json::array(); + // Only print schema validation info if any validation was requested + if (schema_validation_str != yaml_helper::validation_none) + { + bool schema_valid = schema_validation_str == yaml_helper::validation_ok; + j["schema_valid"] = schema_valid; + j["schema_warnings"] = nlohmann::json::array(); + if (!schema_valid) + { + j["schema_warnings"].push_back(schema_validation_str); + } + } + j["errors"] = nlohmann::json::array(); for(auto &err : errors) { nlohmann::json jerr; @@ -511,7 +534,6 @@ const nlohmann::json& rule_loader::result::as_json(const rules_contents_t& conte } j["warnings"] = nlohmann::json::array(); - for(auto &warn : warnings) { nlohmann::json jwarn; diff --git a/userspace/falco/app/actions/validate_rules_files.cpp b/userspace/falco/app/actions/validate_rules_files.cpp index 0ab57272b98..7edeb0423e7 100644 --- a/userspace/falco/app/actions/validate_rules_files.cpp +++ b/userspace/falco/app/actions/validate_rules_files.cpp @@ -68,21 +68,22 @@ falco::app::run_result falco::app::actions::validate_rules_files(falco::app::sta // printed when verbose is true. std::string summary; + falco_logger::log(falco_logger::level::INFO, "Validating rules file(s):\n"); + for(const auto& file : s.options.validate_rules_filenames) + { + falco_logger::log(falco_logger::level::INFO, " " + file + "\n"); + } + // The json output encompasses all files so the // validation result is a single json object. std::string err = ""; nlohmann::json results = nlohmann::json::array(); - falco_logger::log(falco_logger::level::INFO, "Validating rules file(s):\n"); for(auto &filename : s.options.validate_rules_filenames) { std::unique_ptr res; res = s.engine->load_rules(rc.at(filename), filename); - - auto priority = res->schema_validation() == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; - falco_logger::log(priority, std::string(" ") + filename + " | schema validation: " + res->schema_validation() + "\n"); - if (!check_rules_plugin_requirements(s, err)) { return run_result::fatal(err); From 6630176bbe98993578d2e1e925e82fe0df7de5e1 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Tue, 10 Sep 2024 11:36:56 +0200 Subject: [PATCH 8/9] chore(userspace,unit_tests): properly report all schema validation warnings from yaml_helper::validate_node(). `-V` option will print all warnings, while normal run will only print foremost warning. Signed-off-by: Federico Di Pierro --- .../falco/test_configuration_schema.cpp | 6 +- userspace/engine/rule_loader.cpp | 68 +++++++++++++++---- userspace/engine/rule_loader.h | 4 +- userspace/engine/rule_loader_reader.cpp | 6 +- userspace/engine/yaml_helper.h | 56 +++++++-------- userspace/falco/configuration.cpp | 18 +++-- 6 files changed, 103 insertions(+), 55 deletions(-) diff --git a/unit_tests/falco/test_configuration_schema.cpp b/unit_tests/falco/test_configuration_schema.cpp index b0af76fc4e5..9a740fd7e0e 100644 --- a/unit_tests/falco/test_configuration_schema.cpp +++ b/unit_tests/falco/test_configuration_schema.cpp @@ -111,14 +111,14 @@ TEST(Configuration, schema_yaml_helper_validator) EXPECT_NO_THROW(conf.load_from_string(sample_yaml)); // We pass a string variable but not a schema - std::string validation; + std::vector validation; EXPECT_NO_THROW(conf.load_from_string(sample_yaml, nlohmann::json{}, &validation)); - EXPECT_EQ(validation, yaml_helper::validation_none); + EXPECT_EQ(validation[0], yaml_helper::validation_none); // We pass a schema but not a string storage for the validation; no validation takes place EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, nullptr)); // We pass everything EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, &validation)); - EXPECT_EQ(validation, yaml_helper::validation_ok); + EXPECT_EQ(validation[0], yaml_helper::validation_ok); } \ No newline at end of file diff --git a/userspace/engine/rule_loader.cpp b/userspace/engine/rule_loader.cpp index 01e8c089507..3639fbc127d 100644 --- a/userspace/engine/rule_loader.cpp +++ b/userspace/engine/rule_loader.cpp @@ -283,8 +283,7 @@ std::string rule_loader::context::snippet(const falco::load_result::rules_conten rule_loader::result::result(const std::string &name) : name(name), - success(true), - schema_validation_str(yaml_helper::validation_none) + success(true) { } @@ -300,7 +299,11 @@ bool rule_loader::result::has_warnings() std::string rule_loader::result::schema_validation() { - return schema_validation_str; + if (schema_validation_status.empty()) + { + return yaml_helper::validation_none; + } + return schema_validation_status[0]; } void rule_loader::result::add_error(load_result::error_code ec, const std::string& msg, const context& ctx) @@ -318,9 +321,9 @@ void rule_loader::result::add_warning(load_result::warning_code wc, const std::s warnings.push_back(warn); } -void rule_loader::result::set_schema_validation_status(const std::string& status) +void rule_loader::result::set_schema_validation_status(const std::vector& status) { - schema_validation_str = status; + schema_validation_status = status; } const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents) @@ -364,9 +367,28 @@ const std::string& rule_loader::result::as_summary_string() } // Only print schema validation info if any validation was requested - if (schema_validation_str != yaml_helper::validation_none) + if (!schema_validation_status.empty()) { - os << " | schema validation: " << schema_validation_str; + bool schema_valid = schema_validation() == yaml_helper::validation_ok; + // Only print info when there are validation warnings + if (!schema_valid) + { + os << std::endl; + + os << schema_validation_status.size() << " schema warnings: ["; + bool first = true; + for(auto& status : schema_validation_status) + { + if(!first) + { + os << " "; + } + first = false; + + os << status; + } + os << "]"; + } } if(!errors.empty()) @@ -442,9 +464,28 @@ const std::string& rule_loader::result::as_verbose_string(const rules_contents_t } // Only print schema validation info if any validation was requested - if (schema_validation_str != yaml_helper::validation_none) + if (!schema_validation_status.empty()) { - os << " | schema validation: " << schema_validation_str; + bool schema_valid = schema_validation() == yaml_helper::validation_ok; + // Only print info when there are validation warnings + if (!schema_valid) + { + os << std::endl; + + os << schema_validation_status.size() << " schema warnings: ["; + bool first = true; + for(auto& status : schema_validation_status) + { + if(!first) + { + os << " "; + } + first = false; + + os << status; + } + os << "]"; + } } if (!errors.empty()) @@ -507,14 +548,17 @@ const nlohmann::json& rule_loader::result::as_json(const rules_contents_t& conte j["successful"] = success; // Only print schema validation info if any validation was requested - if (schema_validation_str != yaml_helper::validation_none) + if (!schema_validation_status.empty()) { - bool schema_valid = schema_validation_str == yaml_helper::validation_ok; + bool schema_valid = schema_validation() == yaml_helper::validation_ok; j["schema_valid"] = schema_valid; j["schema_warnings"] = nlohmann::json::array(); if (!schema_valid) { - j["schema_warnings"].push_back(schema_validation_str); + for (const auto &schema_warning : schema_validation_status) + { + j["schema_warnings"].push_back(schema_warning); + } } } diff --git a/userspace/engine/rule_loader.h b/userspace/engine/rule_loader.h index 2cbbca6add8..25f047d4099 100644 --- a/userspace/engine/rule_loader.h +++ b/userspace/engine/rule_loader.h @@ -248,7 +248,7 @@ namespace rule_loader const std::string& msg, const context& ctx); - void set_schema_validation_status(const std::string& status); + void set_schema_validation_status(const std::vector& status); std::string schema_validation(); protected: @@ -256,7 +256,7 @@ namespace rule_loader const std::string& as_verbose_string(const falco::load_result::rules_contents_t& contents); std::string name; bool success; - std::string schema_validation_str; + std::vector schema_validation_status; std::vector errors; std::vector warnings; diff --git a/userspace/engine/rule_loader_reader.cpp b/userspace/engine/rule_loader_reader.cpp index 6148a21ac88..50f045f9c63 100644 --- a/userspace/engine/rule_loader_reader.cpp +++ b/userspace/engine/rule_loader_reader.cpp @@ -788,11 +788,11 @@ bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& colle { std::vector docs; yaml_helper reader; - std::string schema_validation; + std::vector schema_warnings; rule_loader::context ctx(cfg.name); try { - docs = reader.loadall_from_string(cfg.content, schema, &schema_validation); + docs = reader.loadall_from_string(cfg.content, schema, &schema_warnings); } catch (YAML::ParserException& e) { @@ -810,7 +810,7 @@ bool rule_loader::reader::read(rule_loader::configuration& cfg, collector& colle cfg.res->add_error(falco::load_result::LOAD_ERR_YAML_PARSE, "unknown YAML parsing error", ctx); return false; } - cfg.res->set_schema_validation_status(schema_validation); + cfg.res->set_schema_validation_status(schema_warnings); for (auto doc = docs.begin(); doc != docs.end(); doc++) { if (doc->IsDefined() && !doc->IsNull()) diff --git a/userspace/engine/yaml_helper.h b/userspace/engine/yaml_helper.h index 07ad8960b40..1220fad0b92 100644 --- a/userspace/engine/yaml_helper.h +++ b/userspace/engine/yaml_helper.h @@ -90,27 +90,23 @@ class yaml_helper * Load all the YAML document represented by the input string. * Since this is used by rule loader, does not process env vars. */ - std::vector loadall_from_string(const std::string& input, const nlohmann::json& schema={}, std::string *validation=nullptr) + std::vector loadall_from_string(const std::string& input, const nlohmann::json& schema={}, std::vector *schema_warnings=nullptr) { auto nodes = YAML::LoadAll(input); - if (validation) + if (schema_warnings) { + schema_warnings->clear(); if(!schema.empty()) { // Validate each node. - for (const auto& node : nodes) + for(const auto& node : nodes) { - *validation = validate_node(node, schema); - if (*validation != validation_ok) - { - // Return first error - break; - } + validate_node(node, schema, schema_warnings); } } else { - *validation = validation_none; + schema_warnings->push_back(validation_none); } } return nodes; @@ -119,20 +115,21 @@ class yaml_helper /** * Load the YAML document represented by the input string. */ - void load_from_string(const std::string& input, const nlohmann::json& schema={}, std::string *validation=nullptr) + void load_from_string(const std::string& input, const nlohmann::json& schema={}, std::vector *schema_warnings=nullptr) { m_root = YAML::Load(input); pre_process_env_vars(m_root); - if (validation) + if (schema_warnings) { + schema_warnings->clear(); if(!schema.empty()) { - *validation = validate_node(m_root, schema); + validate_node(m_root, schema, schema_warnings); } else { - *validation = validation_none; + schema_warnings->push_back(validation_none); } } } @@ -140,14 +137,14 @@ class yaml_helper /** * Load the YAML document from the given file path. */ - void load_from_file(const std::string& path, const nlohmann::json& schema={}, std::string *validation=nullptr) + void load_from_file(const std::string& path, const nlohmann::json& schema={}, std::vector *schema_warnings=nullptr) { - m_root = load_from_file_int(path, schema, validation); + m_root = load_from_file_int(path, schema, schema_warnings); } - void include_config_file(const std::string& include_file_path, const nlohmann::json& schema={}, std::string *validation=nullptr) + void include_config_file(const std::string& include_file_path, const nlohmann::json& schema={}, std::vector *schema_warnings=nullptr) { - auto loaded_nodes = load_from_file_int(include_file_path, schema, validation); + auto loaded_nodes = load_from_file_int(include_file_path, schema, schema_warnings); for(auto n : loaded_nodes) { /* @@ -243,26 +240,27 @@ class yaml_helper private: YAML::Node m_root; - YAML::Node load_from_file_int(const std::string& path, const nlohmann::json& schema={}, std::string *validation=nullptr) + YAML::Node load_from_file_int(const std::string& path, const nlohmann::json& schema, std::vector *schema_warnings) { auto root = YAML::LoadFile(path); pre_process_env_vars(root); - if (validation) + if (schema_warnings) { + schema_warnings->clear(); if(!schema.empty()) { - *validation = validate_node(root, schema); + validate_node(root, schema, schema_warnings); } else { - *validation = validation_none; + schema_warnings->push_back(validation_none); } } return root; } - std::string validate_node(const YAML::Node &node, const nlohmann::json& schema={}) + void validate_node(const YAML::Node &node, const nlohmann::json& schema, std::vector *schema_warnings) { // Validate the yaml against our json schema valijson::Schema schemaDef; @@ -277,16 +275,18 @@ class yaml_helper { valijson::ValidationResults::Error error; // report only the top-most error - if (validationResults.popError(error)) + while (validationResults.popError(error)) { - return std::string(validation_failed + " for ") + schema_warnings->push_back(std::string(validation_failed + " for ") + std::accumulate(error.context.begin(), error.context.end(), std::string("")) + ": " - + error.description; + + error.description); } - return validation_failed; } - return validation_ok; + else + { + schema_warnings->push_back(validation_ok); + } } /* diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 0e9a49cea42..a9c886442ad 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -93,12 +93,13 @@ falco_configuration::falco_configuration(): config_loaded_res falco_configuration::init_from_content(const std::string& config_content, const std::vector& cmdline_options, const std::string& filename) { config_loaded_res res; - std::string validation_status; + std::vector validation_status; m_config.load_from_string(config_content, m_config_schema, &validation_status); init_cmdline_options(cmdline_options); - res[filename] = validation_status; + // Only report top most schema validation status + res[filename] = validation_status[0]; load_yaml(filename); return res; @@ -107,7 +108,7 @@ config_loaded_res falco_configuration::init_from_content(const std::string& conf config_loaded_res falco_configuration::init_from_file(const std::string& conf_filename, const std::vector &cmdline_options) { config_loaded_res res; - std::string validation_status; + std::vector validation_status; try { m_config.load_from_file(conf_filename, m_config_schema, &validation_status); @@ -119,7 +120,8 @@ config_loaded_res falco_configuration::init_from_file(const std::string& conf_fi } init_cmdline_options(cmdline_options); - res[conf_filename] = validation_status; + // Only report top most schema validation status + res[conf_filename] = validation_status[0]; merge_config_files(conf_filename, res); load_yaml(conf_filename); @@ -138,7 +140,7 @@ std::string falco_configuration::dump() // filenames and folders specified in config (minus the skipped ones). void falco_configuration::merge_config_files(const std::string& config_name, config_loaded_res &res) { - std::string validation_status; + std::vector validation_status; m_loaded_configs_filenames.push_back(config_name); const auto ppath = std::filesystem::path(config_name); // Parse files to be included @@ -161,7 +163,8 @@ void falco_configuration::merge_config_files(const std::string& config_name, con { m_loaded_configs_filenames.push_back(include_file); m_config.include_config_file(include_file_path.string(), m_config_schema, &validation_status); - res[include_file_path.string()] = validation_status; + // Only report top most schema validation status + res[include_file_path.string()] = validation_status[0]; } else if (std::filesystem::is_directory(include_file_path)) { @@ -180,7 +183,8 @@ void falco_configuration::merge_config_files(const std::string& config_name, con for (const auto &f : v) { m_config.include_config_file(f, m_config_schema, &validation_status); - res[f] = validation_status; + // Only report top most schema validation status + res[f] = validation_status[0]; } } } From 054c091207fe3d73bee532a0aa09b8bd8fe4fe10 Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Tue, 10 Sep 2024 13:56:44 +0200 Subject: [PATCH 9/9] chore(userspace): adjusted `rule_loader::result::as_verbose_string` following errors and warnings output layout. Signed-off-by: Federico Di Pierro --- userspace/engine/rule_loader.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/userspace/engine/rule_loader.cpp b/userspace/engine/rule_loader.cpp index 3639fbc127d..c04ab3b47cc 100644 --- a/userspace/engine/rule_loader.cpp +++ b/userspace/engine/rule_loader.cpp @@ -375,7 +375,7 @@ const std::string& rule_loader::result::as_summary_string() { os << std::endl; - os << schema_validation_status.size() << " schema warnings: ["; + os << " " << schema_validation_status.size() << " schema warnings: ["; bool first = true; for(auto& status : schema_validation_status) { @@ -472,22 +472,17 @@ const std::string& rule_loader::result::as_verbose_string(const rules_contents_t { os << std::endl; - os << schema_validation_status.size() << " schema warnings: ["; - bool first = true; + os << schema_validation_status.size() + << " Schema warnings:" << std::endl; + for(auto& status : schema_validation_status) { - if(!first) - { - os << " "; - } - first = false; - - os << status; + os << "------" << std::endl; + os << status << std::endl; } - os << "]"; + os << "------" << std::endl; } } - if (!errors.empty()) { os << std::endl;