diff --git a/userspace/engine/CMakeLists.txt b/userspace/engine/CMakeLists.txt index 0cdea720..8218a4d6 100644 --- a/userspace/engine/CMakeLists.txt +++ b/userspace/engine/CMakeLists.txt @@ -19,6 +19,7 @@ set(FALCO_ENGINE_SOURCE_FILES formats.cpp filter_macro_resolver.cpp rule_loader.cpp + rule_reader.cpp stats_manager.cpp) add_library(falco_engine STATIC ${FALCO_ENGINE_SOURCE_FILES}) diff --git a/userspace/engine/rule_reader.cpp b/userspace/engine/rule_reader.cpp new file mode 100644 index 00000000..c4abd24e --- /dev/null +++ b/userspace/engine/rule_reader.cpp @@ -0,0 +1,325 @@ +/* +Copyright (C) 2022 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 "rule_reader.h" + +#define THROW(cond, err) { if (cond) { throw falco_exception(err); } } + +static rule_loader::mark yaml_get_mark( + const string& content, + const vector& docs, + vector::iterator doc, + YAML::iterator node) +{ + rule_loader::mark m; + YAML::Node item = *node++; + YAML::Node cur_doc = *doc++; + // include the "- " sequence mark + size_t from = item.Mark().pos - 2; + size_t to = 0; + if (node != cur_doc.end()) + { + // end of item is beginning of next item + to = node->Mark().pos - 2; + } + else if (doc != docs.end()) + { + // end of item is beginning of next doc + to = doc->Mark().pos - 4; + } + else + { + // end of item is end of file contents + to = content.length(); + } + m.content = content.substr(from, to - from); + m.content = trim(m.content); + return m; +} + +template +static bool decode_val(const YAML::Node& v, T& out) +{ + return v.IsDefined() && v.IsScalar() && YAML::convert::decode(v, out); +} + +template +static bool decode_seq(const YAML::Node& item, vector& out) +{ + if (item.IsDefined() && item.IsSequence()) + { + T value; + for(const YAML::Node& v : item) + { + THROW(!v.IsScalar() || !YAML::convert::decode(v, value), + "Can't decode YAML sequence value: " + YAML::Dump(v)); + out.push_back(value); + } + return true; + } + return false; +} + +template +static bool decode_seq(const YAML::Node& item, set& out) +{ + if (item.IsDefined() && item.IsSequence()) + { + T value; + for(const YAML::Node& v : item) + { + THROW(!v.IsScalar() || !YAML::convert::decode(v, value), + "Can't decode YAML sequence value: " + YAML::Dump(v)); + out.insert(value); + } + return true; + } + return false; +} + +static bool decode_exception_info_entry( + const YAML::Node& item, + rule_loader::rule_exception_info::entry& out) +{ + if (item.IsDefined()) + { + if (item.IsScalar()) + { + out.is_list = false; + if (YAML::convert::decode(item, out.item)) + { + return true; + } + } + if (item.IsSequence()) + { + out.is_list = true; + rule_loader::rule_exception_info::entry tmp; + for(const YAML::Node& v : item) + { + if (!decode_exception_info_entry(v, tmp)) + { + return false; + } + out.items.push_back(tmp); + } + return true; + } + } + return false; +} + +static void read_rule_exceptions( + const YAML::Node& item, + rule_loader::rule_info& v) +{ + THROW(!item.IsSequence(), "Rule exceptions must be a sequence"); + for (auto &ex : item) + { + rule_loader::rule_exception_info v_ex; + THROW(!decode_val(ex["name"], v_ex.name) || v_ex.name.empty(), + "Rule exception item must have name property"); + // note: the legacy lua loader used to throw a "xxx must strings" error + decode_exception_info_entry(ex["fields"], v_ex.fields); + decode_exception_info_entry(ex["comps"], v_ex.comps); + if (ex["values"].IsDefined()) + { + THROW(!ex["values"].IsSequence(), + "Rule exception values must be a sequence"); + for (auto &val : ex["values"]) + { + rule_loader::rule_exception_info::entry v_ex_val; + decode_exception_info_entry(val, v_ex_val); + v_ex.values.push_back(v_ex_val); + } + } + v.exceptions.push_back(v_ex); + } +} + +static void read_item( + rule_loader::context& ctx, + rule_loader& loader, + const YAML::Node& item, + const rule_loader::mark& m) +{ + if (item["required_engine_version"].IsDefined()) + { + rule_loader::engine_version_info v; + THROW(!decode_val(item["required_engine_version"], v.version), + "Value of required_engine_version must be a number"); + loader.define(ctx, v); + } + else if(item["required_plugin_versions"].IsDefined()) + { + THROW(!item["required_plugin_versions"].IsSequence(), + "Value of required_plugin_versions must be a sequence"); + + for(const YAML::Node& plugin : item["required_plugin_versions"]) + { + rule_loader::plugin_version_info v; + THROW(!decode_val(plugin["name"], v.name) || v.name.empty(), + "required_plugin_versions item must have name property"); + THROW(!decode_val(plugin["version"], v.version) || v.version.empty(), + "required_plugin_versions item must have version property"); + loader.define(ctx, v); + } + } + else if(item["list"].IsDefined()) + { + rule_loader::list_info v; + v.context = m; + bool append = false; + THROW(!decode_val(item["list"], v.name) || v.name.empty(), + "List name is empty"); + THROW(!decode_seq(item["items"], v.items), + "List must have property items"); + if(decode_val(item["append"], append) && append) + { + loader.append(ctx, v); + } + else + { + loader.define(ctx, v); + } + } + else if(item["macro"].IsDefined()) + { + rule_loader::macro_info v; + v.context = m; + bool append = false; + v.source = falco_common::syscall_source; + THROW(!decode_val(item["macro"], v.name) || v.name.empty(), + "Macro name is empty"); + THROW(!decode_val(item["condition"], v.cond) || v.cond.empty(), + "Macro must have property condition"); + decode_val(item["source"], v.source); + if(decode_val(item["append"], append) && append) + { + loader.append(ctx, v); + } + else + { + loader.define(ctx, v); + } + } + else if(item["rule"].IsDefined()) + { + rule_loader::rule_info v; + v.context = m; + bool append = false; + v.enabled = true; + v.warn_evttypes = true; + v.skip_if_unknown_filter = false; + THROW(!decode_val(item["rule"], v.name) || v.name.empty(), + "Rule name is empty"); + if(decode_val(item["append"], append) && append) + { + decode_val(item["condition"], v.cond); + if (item["exceptions"].IsDefined()) + { + read_rule_exceptions(item["exceptions"], v); + } + loader.append(ctx, v); + } + else + { + string priority; + bool has_enabled = decode_val(item["enabled"], v.enabled); + bool has_defs = decode_val(item["condition"], v.cond) + && decode_val(item["output"], v.output) + && decode_val(item["desc"], v.desc) + && decode_val(item["priority"], priority); + if (!has_defs) + { + THROW(!has_enabled, "Rule must have properties 'condition', 'output', 'desc', and 'priority'"); + loader.enable(ctx, v); + } + else + { + v.output = trim(v.output); + v.source = falco_common::syscall_source; + THROW(!falco_common::parse_priority(priority, v.priority), + "Invalid priority"); + decode_val(item["source"], v.source); + decode_val(item["warn_evttypes"], v.warn_evttypes); + decode_val(item["skip-if-unknown-filter"], v.skip_if_unknown_filter); + decode_seq(item["tags"], v.tags); + if (item["exceptions"].IsDefined()) + { + read_rule_exceptions(item["exceptions"], v); + } + loader.define(ctx, v); + } + } + } + else + { + ctx.warnings.push_back("Unknown top level object"); + } +} + +bool rule_reader::load(rule_loader::context& ctx, rule_loader& loader) +{ + std::vector docs; + try + { + docs = YAML::LoadAll(ctx.content); + } + catch(const exception& e) + { + ctx.errors.push_back("Could not load YAML file: " + string(e.what())); + return false; + } + + for (auto doc = docs.begin(); doc != docs.end(); doc++) + { + if (doc->IsDefined() && !doc->IsNull()) + { + if(!doc->IsMap() && !doc->IsSequence()) + { + ctx.errors.push_back("Rules content is not yaml"); + return false; + } + if(!doc->IsSequence()) + { + ctx.errors.push_back( + "Rules content is not yaml array of objects"); + return false; + } + for (auto it = doc->begin(); it != doc->end(); it++) + { + if (!it->IsNull()) + { + rule_loader::mark m = yaml_get_mark(ctx.content, docs, doc, it); + YAML::Node item = *it; + try + { + THROW(!item.IsMap(), "Unexpected element type. " + "Each element should be a yaml associative array."); + read_item(ctx, loader, item, m); + } + catch(const exception& e) + { + ctx.errors.push_back(m.error(e.what())); + return false; + } + } + } + } + } + return true; +} diff --git a/userspace/engine/rule_reader.h b/userspace/engine/rule_reader.h new file mode 100644 index 00000000..7214ff3a --- /dev/null +++ b/userspace/engine/rule_reader.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2022 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. +*/ + +#pragma once + +#include +#include +#include +#include "rule_loader.h" + +/*! + \brief Reads the contents of a ruleset +*/ +class rule_reader +{ +public: + /*! + \brief Reads the contents of a ruleset and uses a loader to store + thew new definitions + */ + virtual bool load(rule_loader::context& ctx, rule_loader& loader); +};