mirror of
https://github.com/falcosecurity/falco.git
synced 2025-07-31 22:16:49 +00:00
new(app): add append_output configuration option with fields and format
Signed-off-by: Luca Guerra <luca@guerra.sh>
This commit is contained in:
parent
00ff9d82ea
commit
d210ed2e4f
@ -36,6 +36,7 @@ add_executable(falco_unit_tests
|
||||
engine/test_add_source.cpp
|
||||
engine/test_alt_rule_loader.cpp
|
||||
engine/test_enable_rule.cpp
|
||||
engine/test_extra_output.cpp
|
||||
engine/test_falco_utils.cpp
|
||||
engine/test_filter_details_resolver.cpp
|
||||
engine/test_filter_macro_resolver.cpp
|
||||
@ -47,6 +48,7 @@ add_executable(falco_unit_tests
|
||||
falco/test_configuration_rule_selection.cpp
|
||||
falco/test_configuration_config_files.cpp
|
||||
falco/test_configuration_env_vars.cpp
|
||||
falco/test_configuration_output_options.cpp
|
||||
falco/test_configuration_schema.cpp
|
||||
falco/app/actions/test_select_event_sources.cpp
|
||||
falco/app/actions/test_load_config.cpp
|
||||
|
150
unit_tests/engine/test_extra_output.cpp
Normal file
150
unit_tests/engine/test_extra_output.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
|
||||
#include "../test_falco_engine.h"
|
||||
|
||||
TEST_F(test_falco_engine, extra_format_all)
|
||||
{
|
||||
std::string rules_content = R"END(
|
||||
- rule: legit_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||
priority: INFO
|
||||
)END";
|
||||
|
||||
m_engine->add_extra_output_format("evt.type=%evt.type", "", "", "", false);
|
||||
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||
|
||||
EXPECT_EQ(get_compiled_rule_output("legit_rule"),"user=%user.name command=%proc.cmdline file=%fd.name evt.type=%evt.type");
|
||||
}
|
||||
|
||||
TEST_F(test_falco_engine, extra_format_by_rule)
|
||||
{
|
||||
std::string rules_content = R"END(
|
||||
- rule: legit_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 1
|
||||
priority: INFO
|
||||
|
||||
- rule: another_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 2
|
||||
priority: INFO
|
||||
)END";
|
||||
|
||||
m_engine->add_extra_output_format("evt.type=%evt.type", "", "", "legit_rule", false);
|
||||
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||
|
||||
EXPECT_EQ(get_compiled_rule_output("legit_rule"),"out 1 evt.type=%evt.type");
|
||||
EXPECT_EQ(get_compiled_rule_output("another_rule"),"out 2");
|
||||
}
|
||||
|
||||
TEST_F(test_falco_engine, extra_format_by_tag_rule)
|
||||
{
|
||||
std::string rules_content = R"END(
|
||||
- rule: legit_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 1
|
||||
priority: INFO
|
||||
tags: [tag1]
|
||||
|
||||
- rule: another_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 2
|
||||
priority: INFO
|
||||
tags: [tag1]
|
||||
)END";
|
||||
|
||||
m_engine->add_extra_output_format("extra 1", "", "tag1", "", false);
|
||||
m_engine->add_extra_output_format("extra 2", "", "", "another_rule", false);
|
||||
|
||||
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||
|
||||
EXPECT_EQ(get_compiled_rule_output("legit_rule"),"out 1 extra 1");
|
||||
EXPECT_EQ(get_compiled_rule_output("another_rule"),"out 2 extra 1 extra 2");
|
||||
}
|
||||
|
||||
TEST_F(test_falco_engine, extra_format_replace_container_info)
|
||||
{
|
||||
std::string rules_content = R"END(
|
||||
- rule: legit_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 1 (%container.info)
|
||||
priority: INFO
|
||||
tags: [tag1]
|
||||
|
||||
- rule: another_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 2
|
||||
priority: INFO
|
||||
tags: [tag1]
|
||||
)END";
|
||||
|
||||
m_engine->add_extra_output_format("extra 1", "", "", "", true);
|
||||
|
||||
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||
|
||||
EXPECT_EQ(get_compiled_rule_output("legit_rule"), "out 1 (extra 1)");
|
||||
EXPECT_EQ(get_compiled_rule_output("another_rule"), "out 2 extra 1");
|
||||
}
|
||||
|
||||
TEST_F(test_falco_engine, extra_format_do_not_replace_container_info)
|
||||
{
|
||||
std::string rules_content = R"END(
|
||||
- rule: legit_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: out 1 (%container.info)
|
||||
priority: INFO
|
||||
tags: [tag1]
|
||||
)END";
|
||||
|
||||
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||
|
||||
auto output = get_compiled_rule_output("legit_rule");
|
||||
EXPECT_TRUE(output.find("%container.info") == output.npos);
|
||||
}
|
||||
|
||||
TEST_F(test_falco_engine, extra_fields_all)
|
||||
{
|
||||
std::string rules_content = R"END(
|
||||
- rule: legit_rule
|
||||
desc: legit rule description
|
||||
condition: evt.type=open
|
||||
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||
priority: INFO
|
||||
)END";
|
||||
|
||||
std::unordered_map<std::string, std::string> extra_formatted_fields = {{"my_field", "hello %evt.num"}};
|
||||
for (auto const& f : extra_formatted_fields)
|
||||
{
|
||||
m_engine->add_extra_output_formatted_field(f.first, f.second, "", "", "");
|
||||
}
|
||||
|
||||
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||
|
||||
EXPECT_EQ(get_compiled_rule_formatted_fields("legit_rule"), extra_formatted_fields);
|
||||
}
|
68
unit_tests/falco/test_configuration_output_options.cpp
Normal file
68
unit_tests/falco/test_configuration_output_options.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
#include <falco/configuration.h>
|
||||
|
||||
TEST(ConfigurationRuleOutputOptions, parse_yaml)
|
||||
{
|
||||
falco_configuration falco_config;
|
||||
ASSERT_NO_THROW(falco_config.init_from_content(R"(
|
||||
append_output:
|
||||
- source: syscall
|
||||
tag: persistence
|
||||
rule: some rule name
|
||||
format: "gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4]"
|
||||
|
||||
- tag: persistence
|
||||
fields:
|
||||
- proc.aname[2]: "%proc.aname[2]"
|
||||
- proc.aname[3]: "%proc.aname[3]"
|
||||
- proc.aname[4]: "%proc.aname[4]"
|
||||
format: "gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4]"
|
||||
|
||||
- source: k8s_audit
|
||||
fields:
|
||||
- ka.verb
|
||||
- static_field: "static content"
|
||||
|
||||
)", {}));
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output.size(), 3);
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output[0].m_source, "syscall");
|
||||
EXPECT_EQ(falco_config.m_append_output[0].m_tag, "persistence");
|
||||
EXPECT_EQ(falco_config.m_append_output[0].m_rule, "some rule name");
|
||||
EXPECT_EQ(falco_config.m_append_output[0].m_formatted_fields.size(), 0);
|
||||
EXPECT_EQ(falco_config.m_append_output[0].m_format, "gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4]");
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output[1].m_tag, "persistence");
|
||||
EXPECT_EQ(falco_config.m_append_output[1].m_format, "gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4]");
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output[1].m_formatted_fields.size(), 3);
|
||||
EXPECT_EQ(falco_config.m_append_output[1].m_formatted_fields["proc.aname[2]"], "%proc.aname[2]");
|
||||
EXPECT_EQ(falco_config.m_append_output[1].m_formatted_fields["proc.aname[3]"], "%proc.aname[3]");
|
||||
EXPECT_EQ(falco_config.m_append_output[1].m_formatted_fields["proc.aname[4]"], "%proc.aname[4]");
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output[2].m_source, "k8s_audit");
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output[2].m_formatted_fields.size(), 1);
|
||||
EXPECT_EQ(falco_config.m_append_output[2].m_formatted_fields["static_field"], "static content");
|
||||
|
||||
EXPECT_EQ(falco_config.m_append_output[2].m_raw_fields.size(), 1);
|
||||
EXPECT_EQ(falco_config.m_append_output[2].m_raw_fields.count("ka.verb"), 1);
|
||||
}
|
@ -85,3 +85,15 @@ std::string test_falco_engine::get_compiled_rule_condition(std::string rule_name
|
||||
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||
return rule_description["rules"][0]["details"]["condition_compiled"].template get<std::string>();
|
||||
}
|
||||
|
||||
std::string test_falco_engine::get_compiled_rule_output(std::string rule_name) const
|
||||
{
|
||||
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||
return rule_description["rules"][0]["details"]["output_compiled"].template get<std::string>();
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> test_falco_engine::get_compiled_rule_formatted_fields(std::string rule_name) const
|
||||
{
|
||||
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||
return rule_description["rules"][0]["details"]["extra_output_formatted_fields"].template get<std::unordered_map<std::string, std::string>>();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "rule_loading_messages.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <unordered_map>
|
||||
|
||||
class test_falco_engine : public testing::Test
|
||||
{
|
||||
@ -19,6 +20,8 @@ protected:
|
||||
bool check_warning_message(const std::string& warning_msg) const;
|
||||
bool check_error_message(const std::string& error_msg) const;
|
||||
std::string get_compiled_rule_condition(std::string rule_name = "") const;
|
||||
std::string get_compiled_rule_output(std::string rule_name = "") const;
|
||||
std::unordered_map<std::string, std::string> get_compiled_rule_formatted_fields(std::string rule_name) const;
|
||||
|
||||
std::string m_sample_ruleset = "sample-ruleset";
|
||||
std::string m_sample_source = falco_common::syscall_source;
|
||||
|
@ -57,8 +57,7 @@ falco_engine::falco_engine(bool seed_rng)
|
||||
m_rule_compiler(std::make_shared<rule_loader::compiler>()),
|
||||
m_next_ruleset_id(0),
|
||||
m_min_priority(falco_common::PRIORITY_DEBUG),
|
||||
m_sampling_ratio(1), m_sampling_multiplier(0),
|
||||
m_replace_container_info(false)
|
||||
m_sampling_ratio(1), m_sampling_multiplier(0)
|
||||
{
|
||||
if(seed_rng)
|
||||
{
|
||||
@ -76,6 +75,7 @@ falco_engine::~falco_engine()
|
||||
m_rule_collector->clear();
|
||||
m_rule_stats_manager.clear();
|
||||
m_sources.clear();
|
||||
m_extra_output_format.clear();
|
||||
}
|
||||
|
||||
sinsp_version falco_engine::engine_version()
|
||||
@ -194,8 +194,8 @@ void falco_engine::list_fields(const std::string &source, bool verbose, bool nam
|
||||
std::unique_ptr<load_result> falco_engine::load_rules(const std::string &rules_content, const std::string &name)
|
||||
{
|
||||
rule_loader::configuration cfg(rules_content, m_sources, name);
|
||||
cfg.output_extra = m_extra;
|
||||
cfg.replace_output_container_info = m_replace_container_info;
|
||||
cfg.extra_output_format = m_extra_output_format;
|
||||
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))
|
||||
@ -455,6 +455,7 @@ std::unique_ptr<std::vector<falco_engine::rule_result>> falco_engine::process_ev
|
||||
rule_result.priority_num = rule.priority;
|
||||
rule_result.tags = rule.tags;
|
||||
rule_result.exception_fields = rule.exception_fields;
|
||||
rule_result.extra_output_fields = rule.extra_output_fields;
|
||||
m_rule_stats_manager.on_event(rule);
|
||||
res->push_back(rule_result);
|
||||
}
|
||||
@ -646,9 +647,22 @@ void falco_engine::get_json_details(
|
||||
out["details"]["condition_operators"] = sequence_to_json_array(compiled_details.operators);
|
||||
out["details"]["condition_fields"] = sequence_to_json_array(compiled_details.fields);
|
||||
|
||||
// Get extra requested fields
|
||||
std::vector<std::string> out_fields;
|
||||
|
||||
for(auto const& f : r.extra_output_fields)
|
||||
{
|
||||
// add all the field keys
|
||||
out_fields.emplace_back(f.second.first);
|
||||
|
||||
if (!f.second.second) // formatted field
|
||||
{
|
||||
out["details"]["extra_output_formatted_fields"][f.first] = f.second.first;
|
||||
}
|
||||
}
|
||||
|
||||
// Get fields from output string
|
||||
auto fmt = create_formatter(r.source, r.output);
|
||||
std::vector<std::string> out_fields;
|
||||
fmt->get_field_names(out_fields);
|
||||
out["details"]["output_fields"] = sequence_to_json_array(out_fields);
|
||||
|
||||
@ -1082,10 +1096,37 @@ void falco_engine::set_sampling_multiplier(double sampling_multiplier)
|
||||
m_sampling_multiplier = sampling_multiplier;
|
||||
}
|
||||
|
||||
void falco_engine::set_extra(const std::string &extra, bool replace_container_info)
|
||||
void falco_engine::add_extra_output_format(
|
||||
const std::string &format,
|
||||
const std::string &source,
|
||||
const std::string &tag,
|
||||
const std::string &rule,
|
||||
bool replace_container_info
|
||||
)
|
||||
{
|
||||
m_extra = extra;
|
||||
m_replace_container_info = replace_container_info;
|
||||
m_extra_output_format.push_back({format, source, tag, rule, replace_container_info});
|
||||
}
|
||||
|
||||
void falco_engine::add_extra_output_formatted_field(
|
||||
const std::string &key,
|
||||
const std::string &format,
|
||||
const std::string &source,
|
||||
const std::string &tag,
|
||||
const std::string &rule
|
||||
)
|
||||
{
|
||||
m_extra_output_fields.push_back({key, format, source, tag, rule, false});
|
||||
}
|
||||
|
||||
void falco_engine::add_extra_output_raw_field(
|
||||
const std::string &key,
|
||||
const std::string &source,
|
||||
const std::string &tag,
|
||||
const std::string &rule
|
||||
)
|
||||
{
|
||||
std::string format = "%" + key;
|
||||
m_extra_output_fields.push_back({key, format, source, tag, rule, true});
|
||||
}
|
||||
|
||||
inline bool falco_engine::should_drop_evt() const
|
||||
|
@ -176,15 +176,40 @@ public:
|
||||
//
|
||||
void set_sampling_multiplier(double sampling_multiplier);
|
||||
|
||||
//
|
||||
// You can optionally add "extra" formatting fields to the end
|
||||
// You can optionally add "extra" output to the end
|
||||
// of all output expressions. You can also choose to replace
|
||||
// %container.info with the extra information or add it to the
|
||||
// end of the expression. This is used in open source falco to
|
||||
// add k8s/container information to outputs when
|
||||
// available.
|
||||
//
|
||||
void set_extra(const std::string &extra, bool replace_container_info);
|
||||
void add_extra_output_format(
|
||||
const std::string &format,
|
||||
const std::string &source,
|
||||
const std::string &tag,
|
||||
const std::string &rule,
|
||||
bool replace_container_info
|
||||
);
|
||||
|
||||
// You can optionally add fields that will only show up in the object
|
||||
// output (e.g. json, gRPC) alongside other output_fields
|
||||
// and not in the text message output.
|
||||
// You can add two types of fields: formatted which will act like
|
||||
// an additional output format that appears in the output field
|
||||
void add_extra_output_formatted_field(
|
||||
const std::string &key,
|
||||
const std::string &format,
|
||||
const std::string &source,
|
||||
const std::string &tag,
|
||||
const std::string &rule
|
||||
);
|
||||
|
||||
void add_extra_output_raw_field(
|
||||
const std::string &key,
|
||||
const std::string &source,
|
||||
const std::string &tag,
|
||||
const std::string &rule
|
||||
);
|
||||
|
||||
// Represents the result of matching an event against a set of
|
||||
// rules.
|
||||
@ -196,6 +221,7 @@ public:
|
||||
std::string format;
|
||||
std::set<std::string> exception_fields;
|
||||
std::set<std::string> tags;
|
||||
std::unordered_map<std::string, std::pair<std::string, bool>> extra_output_fields;
|
||||
};
|
||||
|
||||
//
|
||||
@ -461,6 +487,6 @@ private:
|
||||
static const std::string s_default_ruleset;
|
||||
uint32_t m_default_ruleset_id;
|
||||
|
||||
std::string m_extra;
|
||||
bool m_replace_container_info;
|
||||
std::vector<rule_loader::extra_output_format_conf> m_extra_output_format;
|
||||
std::vector<rule_loader::extra_output_field_conf> m_extra_output_fields;
|
||||
};
|
||||
|
@ -79,6 +79,7 @@ struct falco_rule
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string output;
|
||||
std::unordered_map<std::string, std::pair<std::string, bool>> extra_output_fields;
|
||||
std::set<std::string> tags;
|
||||
std::set<std::string> exception_fields;
|
||||
falco_common::priority_type priority;
|
||||
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#include <json/json.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "formats.h"
|
||||
#include "falco_engine.h"
|
||||
@ -35,7 +35,7 @@ falco_formats::~falco_formats()
|
||||
|
||||
std::string falco_formats::format_event(sinsp_evt *evt, const std::string &rule, const std::string &source,
|
||||
const std::string &level, const std::string &format, const std::set<std::string> &tags,
|
||||
const std::string &hostname) const
|
||||
const std::string &hostname, const std::unordered_map<std::string, std::pair<std::string, bool>> &extra_fields) const
|
||||
{
|
||||
std::string line;
|
||||
|
||||
@ -48,27 +48,17 @@ std::string falco_formats::format_event(sinsp_evt *evt, const std::string &rule,
|
||||
|
||||
if(formatter->get_output_format() == sinsp_evt_formatter::OF_JSON)
|
||||
{
|
||||
std::string json_line;
|
||||
std::string json_fields;
|
||||
|
||||
// Format the event into a json object with all fields resolved
|
||||
formatter->tostring(evt, json_line);
|
||||
|
||||
// The formatted string might have a leading newline. If it does, remove it.
|
||||
if(json_line[0] == '\n')
|
||||
{
|
||||
json_line.erase(0, 1);
|
||||
}
|
||||
formatter->tostring(evt, json_fields);
|
||||
|
||||
// For JSON output, the formatter returned a json-as-text
|
||||
// object containing all the fields in the original format
|
||||
// message as well as the event time in ns. Use this to build
|
||||
// a more detailed object containing the event time, rule,
|
||||
// severity, full output, and fields.
|
||||
Json::Value event;
|
||||
Json::Value rule_tags;
|
||||
Json::FastWriter writer;
|
||||
std::string full_line;
|
||||
unsigned int rule_tags_idx = 0;
|
||||
nlohmann::json event;
|
||||
|
||||
// Convert the time-as-nanoseconds to a more json-friendly ISO8601.
|
||||
time_t evttime = evt->get_ts() / 1000000000;
|
||||
@ -94,43 +84,54 @@ std::string falco_formats::format_event(sinsp_evt *evt, const std::string &rule,
|
||||
|
||||
if(m_json_include_tags_property)
|
||||
{
|
||||
if (tags.size() == 0)
|
||||
{
|
||||
// This sets an empty array
|
||||
rule_tags = Json::arrayValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &tag : tags)
|
||||
{
|
||||
rule_tags[rule_tags_idx++] = tag;
|
||||
}
|
||||
}
|
||||
event["tags"] = rule_tags;
|
||||
event["tags"] = tags;
|
||||
}
|
||||
|
||||
full_line = writer.write(event);
|
||||
event["output_fields"] = nlohmann::json::parse(json_fields);
|
||||
|
||||
// Json::FastWriter may add a trailing newline. If it
|
||||
// does, remove it.
|
||||
if(full_line[full_line.length() - 1] == '\n')
|
||||
for (auto const& ef : extra_fields)
|
||||
{
|
||||
full_line.resize(full_line.length() - 1);
|
||||
std::string fformat = ef.second.first;
|
||||
if (fformat.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(fformat[0] == '*'))
|
||||
{
|
||||
fformat = "*" + fformat;
|
||||
}
|
||||
|
||||
if(ef.second.second) // raw field
|
||||
{
|
||||
std::string json_field_map;
|
||||
formatter = m_falco_engine->create_formatter(source, fformat);
|
||||
formatter->tostring_withformat(evt, json_field_map, sinsp_evt_formatter::OF_JSON);
|
||||
auto json_obj = nlohmann::json::parse(json_field_map);
|
||||
event["output_fields"][ef.first] = json_obj[ef.first];
|
||||
} else
|
||||
{
|
||||
event["output_fields"][ef.first] = format_string(evt, fformat, source);
|
||||
}
|
||||
}
|
||||
|
||||
// Cheat-graft the output from the formatter into this
|
||||
// string. Avoids an unnecessary json parse just to
|
||||
// merge the formatted fields at the object level.
|
||||
full_line.pop_back();
|
||||
full_line.append(", \"output_fields\": ");
|
||||
full_line.append(json_line);
|
||||
full_line.append("}");
|
||||
line = full_line;
|
||||
line = event.dump();
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string falco_formats::format_string(sinsp_evt *evt, const std::string &format, const std::string &source) const
|
||||
{
|
||||
std::string line;
|
||||
std::shared_ptr<sinsp_evt_formatter> formatter;
|
||||
|
||||
formatter = m_falco_engine->create_formatter(source, format);
|
||||
formatter->tostring_withformat(evt, line, sinsp_evt_formatter::OF_NORMAL);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> falco_formats::get_field_values(sinsp_evt *evt, const std::string &source,
|
||||
const std::string &format) const
|
||||
{
|
||||
|
@ -31,7 +31,9 @@ public:
|
||||
|
||||
std::string format_event(sinsp_evt *evt, const std::string &rule, const std::string &source,
|
||||
const std::string &level, const std::string &format, const std::set<std::string> &tags,
|
||||
const std::string &hostname) const;
|
||||
const std::string &hostname, const std::unordered_map<std::string, std::pair<std::string, bool>> &extra_fields) const;
|
||||
|
||||
std::string format_string(sinsp_evt *evt, const std::string &format, const std::string &source) const;
|
||||
|
||||
std::map<std::string, std::string> get_field_values(sinsp_evt *evt, const std::string &source,
|
||||
const std::string &format) const ;
|
||||
|
@ -20,6 +20,7 @@ limitations under the License.
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "falco_source.h"
|
||||
@ -261,6 +262,25 @@ namespace rule_loader
|
||||
nlohmann::json res_json;
|
||||
};
|
||||
|
||||
struct extra_output_format_conf
|
||||
{
|
||||
std::string m_format;
|
||||
std::string m_source;
|
||||
std::string m_tag;
|
||||
std::string m_rule;
|
||||
bool m_replace_container_info;
|
||||
};
|
||||
|
||||
struct extra_output_field_conf
|
||||
{
|
||||
std::string m_key;
|
||||
std::string m_format;
|
||||
std::string m_source;
|
||||
std::string m_tag;
|
||||
std::string m_rule;
|
||||
bool m_raw;
|
||||
};
|
||||
|
||||
/*!
|
||||
\brief Contains the info required to load rule definitions
|
||||
*/
|
||||
@ -278,8 +298,9 @@ namespace rule_loader
|
||||
const std::string& content;
|
||||
const indexed_vector<falco_source>& sources;
|
||||
std::string name;
|
||||
std::string output_extra;
|
||||
bool replace_output_container_info = false;
|
||||
|
||||
std::vector<extra_output_format_conf> extra_output_format;
|
||||
std::vector<extra_output_field_conf> extra_output_fields;
|
||||
|
||||
// outputs
|
||||
std::unique_ptr<result> res;
|
||||
|
@ -322,22 +322,6 @@ static std::shared_ptr<ast::expr> parse_condition(
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_output_substitutions(
|
||||
rule_loader::configuration& cfg,
|
||||
std::string& out)
|
||||
{
|
||||
if (out.find(s_container_info_fmt) != std::string::npos)
|
||||
{
|
||||
if (cfg.replace_output_container_info)
|
||||
{
|
||||
out = replace(out, s_container_info_fmt, cfg.output_extra);
|
||||
return;
|
||||
}
|
||||
out = replace(out, s_container_info_fmt, s_default_extra_fmt);
|
||||
}
|
||||
out += cfg.output_extra.empty() ? "" : " " + cfg.output_extra;
|
||||
}
|
||||
|
||||
void rule_loader::compiler::compile_list_infos(
|
||||
configuration& cfg,
|
||||
const collector& col,
|
||||
@ -510,13 +494,64 @@ void rule_loader::compiler::compile_rule_infos(
|
||||
// build rule output message
|
||||
rule.output = r.output;
|
||||
|
||||
// plugins sources do not have any container info and so we won't apply -pk, -pc, etc.
|
||||
// on the other hand, when using plugins you might want to append custom output based on the plugin
|
||||
// TODO: this is not flexible enough (esp. if you mix plugin with syscalls),
|
||||
// it would be better to add configuration options to control the output.
|
||||
if (!cfg.replace_output_container_info || r.source == falco_common::syscall_source)
|
||||
for (auto& extra : cfg.extra_output_format)
|
||||
{
|
||||
apply_output_substitutions(cfg, rule.output);
|
||||
if (extra.m_source != "" && r.source != extra.m_source)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extra.m_tag != "" && r.tags.count(extra.m_tag) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extra.m_rule != "" && r.name != extra.m_rule)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extra.m_replace_container_info)
|
||||
{
|
||||
if (rule.output.find(s_container_info_fmt) != std::string::npos)
|
||||
{
|
||||
rule.output = replace(rule.output, s_container_info_fmt, extra.m_format);
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.output = rule.output + " " + extra.m_format;
|
||||
}
|
||||
} else
|
||||
{
|
||||
rule.output = rule.output + " " + extra.m_format;
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.output.find(s_container_info_fmt) != std::string::npos)
|
||||
{
|
||||
rule.output = replace(rule.output, s_container_info_fmt, s_default_extra_fmt);
|
||||
}
|
||||
|
||||
// build extra output fields if required
|
||||
|
||||
for (auto const& extra : cfg.extra_output_fields)
|
||||
{
|
||||
if (extra.m_source != "" && r.source != extra.m_source)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extra.m_tag != "" && r.tags.count(extra.m_tag) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extra.m_rule != "" && r.name != extra.m_rule)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rule.extra_output_fields[extra.m_key] = {extra.m_format, extra.m_raw};
|
||||
}
|
||||
|
||||
// validate the rule's output
|
||||
@ -538,6 +573,18 @@ void rule_loader::compiler::compile_rule_infos(
|
||||
r.output_ctx);
|
||||
}
|
||||
|
||||
// validate the rule's extra fields if any
|
||||
for (auto const& ef : rule.extra_output_fields)
|
||||
{
|
||||
if(!is_format_valid(*cfg.sources.at(r.source), ef.second.first, err))
|
||||
{
|
||||
throw rule_load_exception(
|
||||
falco::load_result::load_result::LOAD_ERR_COMPILE_OUTPUT,
|
||||
err,
|
||||
r.output_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!compile_condition(cfg,
|
||||
macro_resolver,
|
||||
lists,
|
||||
|
@ -17,49 +17,55 @@ limitations under the License.
|
||||
|
||||
#include "actions.h"
|
||||
#include <libsinsp/plugin_manager.h>
|
||||
#include <falco_common.h>
|
||||
|
||||
using namespace falco::app;
|
||||
using namespace falco::app::actions;
|
||||
|
||||
void configure_output_format(falco::app::state& s)
|
||||
{
|
||||
for (auto& eo : s.config->m_append_output)
|
||||
{
|
||||
if (eo.m_format != "")
|
||||
{
|
||||
s.engine->add_extra_output_format(eo.m_format, eo.m_source, eo.m_tag, eo.m_rule, false);
|
||||
}
|
||||
|
||||
for (auto const& ff : eo.m_formatted_fields)
|
||||
{
|
||||
s.engine->add_extra_output_formatted_field(ff.first, ff.second, eo.m_source, eo.m_tag, eo.m_rule);
|
||||
}
|
||||
|
||||
for (auto const& rf : eo.m_raw_fields)
|
||||
{
|
||||
s.engine->add_extra_output_raw_field(rf, eo.m_source, eo.m_tag, eo.m_rule);
|
||||
}
|
||||
}
|
||||
|
||||
// See https://falco.org/docs/rules/style-guide/
|
||||
const std::string container_info = "container_id=%container.id container_image=%container.image.repository container_image_tag=%container.image.tag container_name=%container.name";
|
||||
const std::string k8s_info = "k8s_ns=%k8s.ns.name k8s_pod_name=%k8s.pod.name";
|
||||
const std::string gvisor_info = "vpid=%proc.vpid vtid=%thread.vtid";
|
||||
|
||||
std::string output_format;
|
||||
bool replace_container_info = false;
|
||||
|
||||
if(s.options.print_additional == "c" || s.options.print_additional == "container")
|
||||
{
|
||||
output_format = container_info;
|
||||
replace_container_info = true;
|
||||
s.engine->add_extra_output_format(container_info, falco_common::syscall_source, "", "", true);
|
||||
}
|
||||
else if(s.options.print_additional == "cg" || s.options.print_additional == "container-gvisor")
|
||||
{
|
||||
output_format = gvisor_info + " " + container_info;
|
||||
replace_container_info = true;
|
||||
s.engine->add_extra_output_format(gvisor_info + " " + container_info, falco_common::syscall_source, "", "", true);
|
||||
}
|
||||
else if(s.options.print_additional == "k" || s.options.print_additional == "kubernetes")
|
||||
{
|
||||
output_format = container_info + " " + k8s_info;
|
||||
replace_container_info = true;
|
||||
s.engine->add_extra_output_format(container_info + " " + k8s_info, falco_common::syscall_source, "", "", true);
|
||||
}
|
||||
else if(s.options.print_additional == "kg" || s.options.print_additional == "kubernetes-gvisor")
|
||||
{
|
||||
output_format = gvisor_info + " " + container_info + " " + k8s_info;
|
||||
replace_container_info = true;
|
||||
s.engine->add_extra_output_format(gvisor_info + " " + container_info + " " + k8s_info, falco_common::syscall_source, "", "", true);
|
||||
}
|
||||
else if(!s.options.print_additional.empty())
|
||||
{
|
||||
output_format = s.options.print_additional;
|
||||
replace_container_info = false;
|
||||
}
|
||||
|
||||
if(!output_format.empty())
|
||||
{
|
||||
s.engine->set_extra(output_format, replace_container_info);
|
||||
s.engine->add_extra_output_format(s.options.print_additional, "", "", "", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,7 +312,9 @@ static falco::app::run_result do_inspect(
|
||||
{
|
||||
for(auto& rule_res : *res)
|
||||
{
|
||||
s.outputs->handle_event(rule_res.evt, rule_res.rule, rule_res.source, rule_res.priority_num, rule_res.format, rule_res.tags);
|
||||
s.outputs->handle_event(
|
||||
rule_res.evt, rule_res.rule, rule_res.source, rule_res.priority_num,
|
||||
rule_res.format, rule_res.tags, rule_res.extra_output_fields);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -588,6 +588,7 @@ void falco_configuration::load_yaml(const std::string& config_name)
|
||||
m_metrics_include_empty_values = m_config.get_scalar<bool>("metrics.include_empty_values", false);
|
||||
|
||||
m_config.get_sequence<std::vector<rule_selection_config>>(m_rules_selection, "rules");
|
||||
m_config.get_sequence<std::vector<append_output_config>>(m_append_output, "append_output");
|
||||
|
||||
std::vector<std::string> load_plugins;
|
||||
|
||||
|
@ -107,6 +107,15 @@ public:
|
||||
std::string m_rule;
|
||||
};
|
||||
|
||||
struct append_output_config {
|
||||
std::string m_source;
|
||||
std::string m_tag;
|
||||
std::string m_rule;
|
||||
std::string m_format;
|
||||
std::unordered_map<std::string, std::string> m_formatted_fields;
|
||||
std::set<std::string> m_raw_fields;
|
||||
};
|
||||
|
||||
falco_configuration();
|
||||
virtual ~falco_configuration() = default;
|
||||
|
||||
@ -134,6 +143,8 @@ public:
|
||||
std::list<std::string> m_loaded_rules_folders;
|
||||
// Rule selection options passed by the user
|
||||
std::vector<rule_selection_config> m_rules_selection;
|
||||
// Append output configuration passed by the user
|
||||
std::vector<append_output_config> m_append_output;
|
||||
|
||||
bool m_json_output;
|
||||
bool m_json_include_output_property;
|
||||
@ -219,6 +230,114 @@ private:
|
||||
};
|
||||
|
||||
namespace YAML {
|
||||
template<>
|
||||
struct convert<falco_configuration::append_output_config> {
|
||||
static Node encode(const falco_configuration::append_output_config & rhs) {
|
||||
Node node;
|
||||
|
||||
if(rhs.m_source != "")
|
||||
{
|
||||
node["source"] = rhs.m_source;
|
||||
}
|
||||
|
||||
if(rhs.m_rule != "")
|
||||
{
|
||||
node["rule"] = rhs.m_rule;
|
||||
}
|
||||
|
||||
if(rhs.m_tag != "")
|
||||
{
|
||||
node["tag"] = rhs.m_tag;
|
||||
}
|
||||
|
||||
if(rhs.m_format != "")
|
||||
{
|
||||
node["format"] = rhs.m_format;
|
||||
}
|
||||
|
||||
for(auto const& field : rhs.m_formatted_fields)
|
||||
{
|
||||
YAML::Node field_node;
|
||||
field_node[field.first] = field.second;
|
||||
node["fields"].push_back(field_node);
|
||||
}
|
||||
|
||||
for(auto const& field : rhs.m_raw_fields)
|
||||
{
|
||||
node["fields"].push_back(field);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static bool decode(const Node& node, falco_configuration::append_output_config & rhs) {
|
||||
if(!node.IsMap())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(node["source"])
|
||||
{
|
||||
rhs.m_source = node["source"].as<std::string>();
|
||||
}
|
||||
|
||||
if(node["tag"])
|
||||
{
|
||||
rhs.m_tag = node["tag"].as<std::string>();
|
||||
}
|
||||
|
||||
if(node["rule"])
|
||||
{
|
||||
rhs.m_rule = node["rule"].as<std::string>();
|
||||
}
|
||||
|
||||
if(node["format"])
|
||||
{
|
||||
rhs.m_format = node["format"].as<std::string>();
|
||||
}
|
||||
|
||||
if(node["fields"])
|
||||
{
|
||||
if(!node["fields"].IsSequence())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for(auto& field_definition : node["fields"])
|
||||
{
|
||||
if(field_definition.IsMap() && field_definition.size() == 1)
|
||||
{
|
||||
YAML::const_iterator def = field_definition.begin();
|
||||
std::string key = def->first.as<std::string>();
|
||||
|
||||
// it is an error to redefine an existing key
|
||||
if (rhs.m_formatted_fields.count(key) != 0 || rhs.m_raw_fields.count(key) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rhs.m_formatted_fields[key] = def->second.as<std::string>();
|
||||
} else if (field_definition.IsScalar())
|
||||
{
|
||||
std::string key = field_definition.as<std::string>();
|
||||
|
||||
// it is an error to redefine an existing key
|
||||
if (rhs.m_formatted_fields.count(key) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rhs.m_raw_fields.insert(key);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct convert<falco_configuration::rule_selection_config> {
|
||||
static Node encode(const falco_configuration::rule_selection_config & rhs) {
|
||||
|
@ -127,7 +127,8 @@ void falco_outputs::add_output(const falco::outputs::config &oc)
|
||||
}
|
||||
|
||||
void falco_outputs::handle_event(sinsp_evt *evt, const std::string &rule, const std::string &source,
|
||||
falco_common::priority_type priority, const std::string &format, std::set<std::string> &tags)
|
||||
falco_common::priority_type priority, const std::string &format, std::set<std::string> &tags,
|
||||
std::unordered_map<std::string, std::pair<std::string, bool>> &extra_fields)
|
||||
{
|
||||
falco_outputs::ctrl_msg cmsg = {};
|
||||
cmsg.ts = evt->get_ts();
|
||||
@ -157,9 +158,30 @@ void falco_outputs::handle_event(sinsp_evt *evt, const std::string &rule, const
|
||||
}
|
||||
|
||||
cmsg.msg = m_formats->format_event(
|
||||
evt, rule, source, falco_common::format_priority(priority), sformat, tags, m_hostname
|
||||
evt, rule, source, falco_common::format_priority(priority), sformat, tags, m_hostname, extra_fields
|
||||
);
|
||||
cmsg.fields = m_formats->get_field_values(evt, source, sformat);
|
||||
|
||||
auto fields = m_formats->get_field_values(evt, source, sformat);
|
||||
for (auto const& ef : extra_fields)
|
||||
{
|
||||
// when formatting for the control message we always want strings,
|
||||
// so we can simply format raw fields as string
|
||||
std::string fformat = ef.second.first;
|
||||
if (fformat.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(fformat[0] == '*'))
|
||||
{
|
||||
fformat = "*" + fformat;
|
||||
}
|
||||
|
||||
fields[ef.first] = m_formats->format_string(evt, fformat, source);
|
||||
}
|
||||
|
||||
cmsg.fields = fields;
|
||||
|
||||
cmsg.tags.insert(tags.begin(), tags.end());
|
||||
|
||||
cmsg.type = ctrl_msg_type::CTRL_MSG_OUTPUT;
|
||||
|
@ -59,7 +59,8 @@ public:
|
||||
is an event that has matched some rule).
|
||||
*/
|
||||
void handle_event(sinsp_evt *evt, const std::string &rule, const std::string &source,
|
||||
falco_common::priority_type priority, const std::string &format, std::set<std::string> &tags);
|
||||
falco_common::priority_type priority, const std::string &format, std::set<std::string> &tags,
|
||||
std::unordered_map<std::string, std::pair<std::string, bool>> &extra_fields);
|
||||
|
||||
/*!
|
||||
\brief Format then send a generic message to all outputs.
|
||||
|
Loading…
Reference in New Issue
Block a user