mirror of
https://github.com/falcosecurity/falco.git
synced 2025-07-05 19:06:44 +00:00
update(userspace/engine): introduce new rule_reader class
The rule_reader class is responsible of parsing the YAML ruleset text and of using the rule_loader to store the new definition in the internal state. This is a first step towards separating the YAML reading logic from the rule parsing one. Potentially, this will allow us to read rulesets from another YAML library or from something different than YAML files too. Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
This commit is contained in:
parent
9ed7d57838
commit
2c0e6d3b88
@ -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})
|
||||
|
325
userspace/engine/rule_reader.cpp
Normal file
325
userspace/engine/rule_reader.cpp
Normal file
@ -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<YAML::Node>& docs,
|
||||
vector<YAML::Node>::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 <typename T>
|
||||
static bool decode_val(const YAML::Node& v, T& out)
|
||||
{
|
||||
return v.IsDefined() && v.IsScalar() && YAML::convert<T>::decode(v, out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool decode_seq(const YAML::Node& item, vector<T>& out)
|
||||
{
|
||||
if (item.IsDefined() && item.IsSequence())
|
||||
{
|
||||
T value;
|
||||
for(const YAML::Node& v : item)
|
||||
{
|
||||
THROW(!v.IsScalar() || !YAML::convert<T>::decode(v, value),
|
||||
"Can't decode YAML sequence value: " + YAML::Dump(v));
|
||||
out.push_back(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool decode_seq(const YAML::Node& item, set<T>& out)
|
||||
{
|
||||
if (item.IsDefined() && item.IsSequence())
|
||||
{
|
||||
T value;
|
||||
for(const YAML::Node& v : item)
|
||||
{
|
||||
THROW(!v.IsScalar() || !YAML::convert<T>::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<string>::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<YAML::Node> 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;
|
||||
}
|
35
userspace/engine/rule_reader.h
Normal file
35
userspace/engine/rule_reader.h
Normal file
@ -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 <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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);
|
||||
};
|
Loading…
Reference in New Issue
Block a user