// SPDX-License-Identifier: Apache-2.0 /* Copyright (C) 2023 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 #include #include #include #include #include #include #include #include "indexed_vector.h" #include "evttype_index_ruleset.h" #include "rule_loader_reader.h" #include "rule_loader_collector.h" #include "rule_loader_compiler.h" namespace { struct test_object_info { std::string name; std::string property; }; struct test_compile_output : public rule_loader::compile_output { test_compile_output() = default; virtual ~test_compile_output() = default; virtual std::unique_ptr clone() const override { return std::make_unique(*this); } std::set defined_test_properties; }; class test_compiler : public rule_loader::compiler { public: test_compiler() = default; virtual ~test_compiler() = default; std::unique_ptr new_compile_output() override { return std::make_unique(); } void compile(rule_loader::configuration& cfg, const rule_loader::collector& col, rule_loader::compile_output& out) const override; }; class test_collector : public rule_loader::collector { public: test_collector() = default; virtual ~test_collector() = default; indexed_vector test_object_infos; }; class test_reader : public rule_loader::reader { public: test_reader() = default; virtual ~test_reader() = default; protected: rule_loader::context create_context(const YAML::Node& item, const rule_loader::context& parent) { return rule_loader::context(item, rule_loader::context::EXTENSION_ITEM, "test object", parent); }; void read_item(rule_loader::configuration& cfg, rule_loader::collector& collector, const YAML::Node& item, const rule_loader::context& parent) override { test_collector& test_col = dynamic_cast(collector); if(item["test_object"].IsDefined()) { rule_loader::context tmp = create_context(item, parent); test_object_info obj; std::string name; std::string property; decode_val(item, "test_object", name, tmp); decode_val(item, "property", property, tmp); obj.name = name; obj.property = property; test_col.test_object_infos.insert(obj, obj.name); } else { rule_loader::reader::read_item(cfg, collector, item, parent); } }; }; class test_ruleset : public evttype_index_ruleset { public: explicit test_ruleset(std::shared_ptr factory): evttype_index_ruleset(factory) {}; virtual ~test_ruleset() = default; void add_compile_output(const rule_loader::compile_output& compile_output, falco_common::priority_type min_priority, const std::string& source) { evttype_index_ruleset::add_compile_output(compile_output, min_priority, source); std::shared_ptr ruleset; get_engine_state().get_ruleset(source, ruleset); EXPECT_EQ(this, ruleset.get()); const test_compile_output& test_output = dynamic_cast(compile_output); defined_properties = test_output.defined_test_properties; }; std::set defined_properties; }; class test_ruleset_factory : public filter_ruleset_factory { public: explicit test_ruleset_factory(std::shared_ptr factory): m_filter_factory(factory) {} virtual ~test_ruleset_factory() = default; inline std::shared_ptr new_ruleset() override { return std::make_shared(m_filter_factory); } std::shared_ptr m_filter_factory; }; }; // namespace void test_compiler::compile(rule_loader::configuration& cfg, const rule_loader::collector& col, rule_loader::compile_output& out) const { rule_loader::compiler::compile(cfg, col, out); const test_collector& test_col = dynamic_cast(col); test_compile_output& test_output = dynamic_cast(out); for(auto& test_obj : test_col.test_object_infos) { test_output.defined_test_properties.insert(test_obj.property); } } static std::string content = R"END( - test_object: test property: my-value - test_object: test2 property: other-value - list: shell_binaries items: [sh, bash] - macro: spawned_process condition: evt.type=execve and proc.name in (shell_binaries) - rule: test info rule desc: A test info rule condition: spawned_process output: A test info rule matched (evt.type=%evt.type proc.name=%proc.name) priority: INFO source: syscall tags: [process] - rule: test k8s_audit rule desc: A k8s audit test rule condition: ka.target.resource=deployments output: A k8s audit rule matched (ka.verb=%ka.verb resource=%ka.target.resource) priority: INFO source: k8s_audit tags: [process] - rule: test debug rule desc: A test debug rule condition: spawned_process and proc.name="bash" output: A test debug rule matched (evt.type=%evt.type proc.name=%proc.name) priority: DEBUG source: syscall tags: [process] )END"; static std::string syscall_source_name = "syscall"; static std::shared_ptr create_configuration( sinsp& inspector, sinsp_filter_check_list& filterchecks, indexed_vector& sources) { auto filter_factory = std::make_shared(&inspector, filterchecks); auto formatter_factory = std::make_shared(&inspector, filterchecks); auto ruleset_factory = std::make_shared(filter_factory); falco_source syscall_source; syscall_source.name = syscall_source_name; syscall_source.ruleset = ruleset_factory->new_ruleset(); syscall_source.ruleset_factory = ruleset_factory; syscall_source.filter_factory = filter_factory; syscall_source.formatter_factory = formatter_factory; sources.insert(syscall_source, syscall_source_name); return std::make_shared(content, sources, "test configuration"); } static void load_rules(sinsp& inspector, sinsp_filter_check_list& filterchecks, std::unique_ptr& compile_output, indexed_vector& sources) { std::shared_ptr cfg = create_configuration(inspector, filterchecks, sources); rule_loader::reader reader; rule_loader::collector collector; rule_loader::compiler compiler; EXPECT_TRUE(reader.read(*cfg, collector)); compile_output = compiler.new_compile_output(); compiler.compile(*cfg, collector, *compile_output); } TEST(engine_loader_alt_loader, load_rules) { sinsp inspector; sinsp_filter_check_list filterchecks; std::unique_ptr compile_output; indexed_vector sources; load_rules(inspector, filterchecks, compile_output, sources); // Note that the k8s_audit rule will be skipped as load_rules // only adds a syscall source. EXPECT_EQ(compile_output->lists.size(), 1); EXPECT_TRUE(compile_output->lists.at("shell_binaries") != nullptr); EXPECT_EQ(compile_output->macros.size(), 1); EXPECT_TRUE(compile_output->macros.at("spawned_process") != nullptr); EXPECT_EQ(compile_output->rules.size(), 2); EXPECT_TRUE(compile_output->rules.at("test info rule") != nullptr); EXPECT_TRUE(compile_output->rules.at("test debug rule") != nullptr); } TEST(engine_loader_alt_loader, pass_compile_output_to_ruleset) { sinsp inspector; sinsp_filter_check_list filterchecks; std::unique_ptr compile_output; indexed_vector sources; load_rules(inspector, filterchecks, compile_output, sources); std::shared_ptr ruleset = sources.at(syscall_source_name)->ruleset; ruleset->add_compile_output(*compile_output, falco_common::PRIORITY_INFORMATIONAL, syscall_source_name); // Enable all rules for a ruleset id. Because the compile // output contained one rule with priority >= INFO, that rule // should be enabled. uint16_t ruleset_id = 0; ruleset->enable("", filter_ruleset::match_type::substring, ruleset_id); EXPECT_EQ(ruleset->enabled_count(ruleset_id), 1); } TEST(engine_loader_alt_loader, falco_engine_alternate_loader) { falco_engine engine; sinsp inspector; sinsp_filter_check_list filterchecks; auto filter_factory = std::make_shared(&inspector, filterchecks); auto formatter_factory = std::make_shared(&inspector, filterchecks); auto ruleset_factory = std::make_shared(filter_factory); engine.add_source(syscall_source_name, filter_factory, formatter_factory, ruleset_factory); auto reader = std::make_shared(); auto collector = std::make_shared(); auto compiler = std::make_shared(); engine.set_rule_reader(reader); engine.set_rule_collector(collector); engine.set_rule_compiler(compiler); EXPECT_EQ(reader, engine.get_rule_reader()); EXPECT_EQ(collector, engine.get_rule_collector()); EXPECT_EQ(compiler, engine.get_rule_compiler()); engine.load_rules(content, "test_rules.yaml"); EXPECT_EQ(collector->test_object_infos.size(), 2); std::shared_ptr ruleset = engine.ruleset_for_source(syscall_source_name); std::set& defined_properties = std::dynamic_pointer_cast(ruleset)->defined_properties; EXPECT_TRUE(defined_properties.find("my-value") != defined_properties.end()); EXPECT_TRUE(defined_properties.find("other-value") != defined_properties.end()); EXPECT_TRUE(defined_properties.find("not-exists-value") == defined_properties.end()); }; TEST(engine_loader_alt_loader, clone_compile_output) { sinsp inspector; sinsp_filter_check_list filterchecks; indexed_vector sources; std::shared_ptr cfg = create_configuration(inspector, filterchecks, sources); test_reader reader; test_collector collector; test_compiler compiler; EXPECT_TRUE(reader.read(*cfg, collector)); std::unique_ptr compile_output = compiler.new_compile_output(); compiler.compile(*cfg, collector, *compile_output); const test_compile_output& original_ref = dynamic_cast(*(compile_output.get())); std::unique_ptr copy = compile_output->clone(); const test_compile_output& copy_ref = dynamic_cast(*(copy.get())); EXPECT_EQ(copy_ref.lists, original_ref.lists); EXPECT_EQ(copy_ref.macros, original_ref.macros); EXPECT_EQ(copy_ref.rules, original_ref.rules); EXPECT_EQ(copy_ref.defined_test_properties, original_ref.defined_test_properties); }