diff --git a/falco.yaml b/falco.yaml index ad65cc0e..0e72e994 100644 --- a/falco.yaml +++ b/falco.yaml @@ -573,6 +573,18 @@ priority: debug # programs to process and consume the data. By default, this option is disabled. json_output: false +# [Incubating] `json_container` +# +# When json_output is enabled, Falco will output alert messages and events as JSON. If +# the json_container is also specified (optional), the event will be placed in that parent +# schema as defined in `falco_formats::format_json_container` +# Supported json containers: +# * `splunk_hec`: a container allowing direct posting to a Splunk HEC endpoint +# * `slack_webhook`: a container allowing direct posting to a Slack webhook endpoint +# * `generic_event_encoded`: a container that double encodes the json object as example container +# * "" or omitted from config: the JSON object without any container is emitted (default behaviour) +json_container: "" + # [Stable] `json_include_output_property` # # When using JSON output in Falco, you have the option to include the "output" @@ -789,6 +801,8 @@ http_output: keep_alive: false # Maximum consecutive timeouts of libcurl to ignore max_consecutive_timeouts: 5 + # if provided, adds the value to the Authorization header in the POST + authorization: "" # [Stable] `program_output` # diff --git a/userspace/engine/formats.cpp b/userspace/engine/formats.cpp index ef99957b..3d1ac351 100644 --- a/userspace/engine/formats.cpp +++ b/userspace/engine/formats.cpp @@ -25,12 +25,14 @@ falco_formats::falco_formats(std::shared_ptr engine, bool json_include_tags_property, bool json_include_message_property, bool json_include_output_fields_property, + const std::string &json_container, bool time_format_iso_8601): m_falco_engine(engine), m_json_include_output_property(json_include_output_property), m_json_include_tags_property(json_include_tags_property), m_json_include_message_property(json_include_message_property), m_json_include_output_fields_property(json_include_output_fields_property), + m_json_container(json_container), m_time_format_iso_8601(time_format_iso_8601) {} falco_formats::~falco_formats() {} @@ -157,7 +159,7 @@ std::string falco_formats::format_event(sinsp_evt *evt, } } - return event.dump(); + return format_json_container(event, evttime); } // should never get here until we only have OF_NORMAL and OF_JSON @@ -197,3 +199,32 @@ std::map falco_formats::get_field_values( return ret; } + +/// @brief Helper function for encoding a json event into a parent container, described by +/// configuration. +/// @param json the json object to encode +/// @param evttime the event time +/// @return returns a std::string encoded json object according to the configured json_container +std::string falco_formats::format_json_container(nlohmann::json &json, time_t evttime) const { + if(m_json_container == std::string("splunk_hec")) { + nlohmann::json omsg; + omsg["time"] = evttime; + omsg["host"] = json["hostname"]; + omsg["source"] = "falco"; + omsg["event"] = json; + return omsg.dump(); + } else if(m_json_container == std::string("slack_webhook")) { + nlohmann::json omsg; + omsg["text"] = json.dump(); + return omsg.dump(); + } else if(m_json_container == std::string("generic_event_encoded")) { + nlohmann::json omsg; + omsg["event"] = json.dump(); + omsg["host"] = json["hostname"]; + omsg["time"] = evttime; + const std::map &const_labels = {{"evt_type", "falco"}}; + omsg["fields"] = const_labels; + return omsg.dump(); + } + return json.dump(); +} diff --git a/userspace/engine/formats.h b/userspace/engine/formats.h index 6518e7be..b5a91078 100644 --- a/userspace/engine/formats.h +++ b/userspace/engine/formats.h @@ -28,6 +28,7 @@ public: bool json_include_tags_property, bool json_include_message_property, bool json_include_output_fields_property, + const std::string &json_container, bool time_format_iso_8601); virtual ~falco_formats(); @@ -47,6 +48,7 @@ public: std::map get_field_values(sinsp_evt *evt, const std::string &source, const std::string &format) const; + std::string format_json_container(nlohmann::json &json, time_t evttime) const; protected: std::shared_ptr m_falco_engine; @@ -54,5 +56,6 @@ protected: bool m_json_include_tags_property; bool m_json_include_message_property; bool m_json_include_output_fields_property; + const std::string m_json_container; bool m_time_format_iso_8601; }; diff --git a/userspace/falco/app/actions/init_outputs.cpp b/userspace/falco/app/actions/init_outputs.cpp index e7de5cf4..fdfc5574 100644 --- a/userspace/falco/app/actions/init_outputs.cpp +++ b/userspace/falco/app/actions/init_outputs.cpp @@ -65,6 +65,7 @@ falco::app::run_result falco::app::actions::init_outputs(falco::app::state& s) { s.config->m_json_include_tags_property, s.config->m_json_include_message_property, s.config->m_json_include_output_fields_property, + s.config->m_json_container, s.config->m_output_timeout, s.config->m_buffered_outputs, s.config->m_outputs_queue_capacity, diff --git a/userspace/falco/config_json_schema.h b/userspace/falco/config_json_schema.h index 277ff7f7..35a819da 100644 --- a/userspace/falco/config_json_schema.h +++ b/userspace/falco/config_json_schema.h @@ -118,6 +118,9 @@ const char config_schema_string[] = LONG_STRING_CONST( "json_output": { "type": "boolean" }, + "json_container": { + "type": "string" + }, "json_include_output_property": { "type": "boolean" }, @@ -546,6 +549,9 @@ const char config_schema_string[] = LONG_STRING_CONST( }, "max_consecutive_timeouts": { "type": "integer" + }, + "authorization": { + "type": "string" } }, "minProperties": 1, diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 67575616..13118203 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -70,6 +70,7 @@ falco_configuration::falco_configuration(): m_json_include_tags_property(true), m_json_include_message_property(false), m_json_include_output_fields_property(true), + m_json_container(""), m_rule_matching(falco_common::rule_matching::FIRST), m_watch_config_files(true), m_buffered_outputs(false), @@ -347,6 +348,7 @@ void falco_configuration::load_yaml(const std::string &config_name) { m_config.get_scalar("json_include_message_property", false); m_json_include_output_fields_property = m_config.get_scalar("json_include_output_fields_property", true); + m_json_container = m_config.get_scalar("json_container", ""); m_outputs.clear(); falco::outputs::config file_output; @@ -461,6 +463,9 @@ void falco_configuration::load_yaml(const std::string &config_name) { m_config.get_scalar("http_output.max_consecutive_timeouts", 5); http_output.options["max_consecutive_timeouts"] = std::to_string(max_consecutive_timeouts); + std::string authorization; + authorization = m_config.get_scalar("http_output.authorization", ""); + http_output.options["authorization"] = authorization; m_outputs.push_back(http_output); } diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index a7809361..3b318305 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -155,6 +155,8 @@ public: bool m_json_include_tags_property; bool m_json_include_message_property; bool m_json_include_output_fields_property; + std::string m_json_container; + std::string m_log_level; std::vector m_outputs; diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index 0023756c..411df094 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -46,6 +46,7 @@ falco_outputs::falco_outputs(std::shared_ptr engine, bool json_include_tags_property, bool json_include_message_property, bool json_include_output_fields_property, + const std::string &json_container, uint32_t timeout, bool buffered, size_t outputs_queue_capacity, @@ -56,6 +57,7 @@ falco_outputs::falco_outputs(std::shared_ptr engine, json_include_tags_property, json_include_message_property, json_include_output_fields_property, + json_container, time_format_iso_8601)), m_buffered(buffered), m_json_output(json_output), @@ -201,7 +203,7 @@ void falco_outputs::handle_msg(uint64_t ts, jmsg["hostname"] = m_hostname; jmsg["source"] = s_internal_source; - cmsg.msg = jmsg.dump(); + cmsg.msg = m_formats->format_json_container(jmsg, evttime); } else { std::string timestr; bool first = true; diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h index adfe0f6e..7e1d0c62 100644 --- a/userspace/falco/falco_outputs.h +++ b/userspace/falco/falco_outputs.h @@ -47,6 +47,7 @@ public: bool json_include_tags_property, bool json_include_message_property, bool json_include_output_fields_property, + const std::string &json_container, uint32_t timeout, bool buffered, size_t outputs_queue_capacity, diff --git a/userspace/falco/outputs_http.cpp b/userspace/falco/outputs_http.cpp index 96c563b6..058c7c31 100644 --- a/userspace/falco/outputs_http.cpp +++ b/userspace/falco/outputs_http.cpp @@ -52,6 +52,11 @@ bool falco::outputs::output_http::init(const config &oc, } else { m_http_headers = curl_slist_append(m_http_headers, "Content-Type: text/plain"); } + if(!m_oc.options["authorization"].empty()) { + m_http_headers = + curl_slist_append(m_http_headers, + ("Authorization: " + m_oc.options["authorization"]).c_str()); + } res = curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_http_headers); // if the URL is quoted the quotes should be removed to satisfy libcurl expected format