Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

new(userspace/falco,userspace/engine): rule json schema validation #3313

Merged
merged 9 commits into from
Sep 11, 2024
80 changes: 68 additions & 12 deletions unit_tests/engine/test_rule_loader.cpp

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions unit_tests/falco/test_configuration_schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> 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);
}
6 changes: 5 additions & 1 deletion userspace/engine/falco_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ 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":["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;

falco_engine::falco_engine(bool seed_rng)
Expand All @@ -67,6 +69,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()
Expand Down Expand Up @@ -198,7 +202,7 @@ std::unique_ptr<load_result> 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();
Expand Down
2 changes: 2 additions & 0 deletions userspace/engine/falco_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ class falco_engine
const std::vector<plugin_version_requirement>& 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.
Expand Down
3 changes: 3 additions & 0 deletions userspace/engine/falco_load_result.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 80 additions & 2 deletions userspace/engine/rule_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ limitations under the License.
#include <string>

#include "rule_loader.h"
#include "yaml_helper.h"


static const std::string item_type_strings[] = {
Expand Down Expand Up @@ -296,6 +297,15 @@ bool rule_loader::result::has_warnings()
return (warnings.size() > 0);
}

std::string rule_loader::result::schema_validation()
{
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)
{
error err = {ec, msg, ctx};
Expand All @@ -311,6 +321,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::vector<std::string>& status)
{
schema_validation_status = status;
}

const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents)
{
if(verbose)
Expand Down Expand Up @@ -351,6 +366,31 @@ const std::string& rule_loader::result::as_summary_string()
os << "Invalid";
}

// Only print schema validation info if any validation was requested
if (!schema_validation_status.empty())
{
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())
{
os << std::endl;
Expand Down Expand Up @@ -423,6 +463,31 @@ 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_status.empty())
{
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())
{
os << std::endl;
Expand Down Expand Up @@ -482,8 +547,22 @@ 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_status.empty())
{
bool schema_valid = schema_validation() == yaml_helper::validation_ok;
j["schema_valid"] = schema_valid;
j["schema_warnings"] = nlohmann::json::array();
if (!schema_valid)
{
for (const auto &schema_warning : schema_validation_status)
{
j["schema_warnings"].push_back(schema_warning);
}
}
}

j["errors"] = nlohmann::json::array();
for(auto &err : errors)
{
nlohmann::json jerr;
Expand All @@ -499,7 +578,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;
Expand Down
4 changes: 4 additions & 0 deletions userspace/engine/rule_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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::vector<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::vector<std::string> schema_validation_status;

std::vector<error> errors;
std::vector<warning> warnings;
Expand Down
9 changes: 6 additions & 3 deletions userspace/engine/rule_loader_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <libsinsp/logger.h>

#include <re2/re2.h>
Expand Down Expand Up @@ -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<YAML::Node> docs;
yaml_helper reader;
std::vector<std::string> schema_warnings;
rule_loader::context ctx(cfg.name);
try
{
docs = YAML::LoadAll(cfg.content);
docs = reader.loadall_from_string(cfg.content, schema, &schema_warnings);
}
catch (YAML::ParserException& e)
{
Expand All @@ -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_warnings);
for (auto doc = docs.begin(); doc != docs.end(); doc++)
{
if (doc->IsDefined() && !doc->IsNull())
Expand Down
2 changes: 1 addition & 1 deletion userspace/engine/rule_loader_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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={});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-breaking change.


/*!
\brief Engine version used to be represented as a simple progressive
Expand Down
Loading
Loading