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/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/falco_engine.cpp b/userspace/engine/falco_engine.cpp index dd2603bf632..f7f79268988 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -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) @@ -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() @@ -198,7 +202,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..c04ab3b47cc 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[] = { @@ -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}; @@ -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& status) +{ + schema_validation_status = status; +} + const std::string& rule_loader::result::as_string(bool verbose, const rules_contents_t& contents) { if(verbose) @@ -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; @@ -423,6 +463,26 @@ 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:" << std::endl; + + for(auto& status : schema_validation_status) + { + os << "------" << std::endl; + os << status << std::endl; + } + os << "------" << std::endl; + } + } if (!errors.empty()) { os << std::endl; @@ -482,8 +542,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; @@ -499,7 +573,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/engine/rule_loader.h b/userspace/engine/rule_loader.h index 3ec15f903a9..25f047d4099 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::vector& 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 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 4d875cb440d..50f045f9c63 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::vector 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) { @@ -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()) 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 87% rename from userspace/falco/yaml_helper.h rename to userspace/engine/yaml_helper.h index d1134873fa6..1220fad0b92 100644 --- a/userspace/falco/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 { @@ -89,25 +84,52 @@ 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::vector *schema_warnings=nullptr) + { + auto nodes = YAML::LoadAll(input); + if (schema_warnings) + { + schema_warnings->clear(); + if(!schema.empty()) + { + // Validate each node. + for(const auto& node : nodes) + { + validate_node(node, schema, schema_warnings); + } + } + else + { + schema_warnings->push_back(validation_none); + } + } + return nodes; + } /** * 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); } } } @@ -115,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) { /* @@ -218,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; @@ -252,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/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/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..ec4050baaf7 100644 --- a/userspace/falco/app/actions/load_rules_files.cpp +++ b/userspace/falco/app/actions/load_rules_files.cpp @@ -66,12 +66,14 @@ 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"); 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 new file mode 100644 index 00000000000..1052c597d54 --- /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.engine->m_rule_schema.dump(2).c_str()); + return run_result::exit(); + } + return run_result::ok(); +} 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..a9c886442ad 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -53,7 +53,7 @@ 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"}}})"; falco_configuration::falco_configuration(): m_json_output(false), @@ -87,18 +87,19 @@ 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); } 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]; } } } diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 7a3281a6c68..33d0addd6ab 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -210,7 +210,6 @@ class falco_configuration replay_config m_replay = {}; gvisor_config m_gvisor = {}; - // Needed by tests yaml_helper m_config; nlohmann::json m_config_schema;