diff --git a/falco.yaml b/falco.yaml index 97ceb8b5608..3cf20201fe7 100644 --- a/falco.yaml +++ b/falco.yaml @@ -365,6 +365,8 @@ http_output: client_cert: "/etc/ssl/certs/client.crt" # Path to the client key. client_key: "/etc/ssl/certs/client.key" + # Whether to echo server answers to stdout + echo: false # [Stable] `program_output` # diff --git a/submodules/falcosecurity-rules b/submodules/falcosecurity-rules index 40a98173303..3f524806184 160000 --- a/submodules/falcosecurity-rules +++ b/submodules/falcosecurity-rules @@ -1 +1 @@ -Subproject commit 40a98173303440ba05df11e3c607e27615429cab +Subproject commit 3f52480618491a9232a1ec6a1f692fc04899c989 diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index d55dde7d8a7..9db7a92e3ec 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -195,6 +195,10 @@ void falco_configuration::load_yaml(const std::string& config_name, const yaml_h insecure = config.get_scalar("http_output.insecure", false); http_output.options["insecure"] = insecure? std::string("true") : std::string("false"); + bool echo; + echo = config.get_scalar("http_output.echo", false); + http_output.options["echo"] = echo? std::string("true") : std::string("false"); + std::string ca_cert; ca_cert = config.get_scalar("http_output.ca_cert", ""); http_output.options["ca_cert"] = ca_cert; diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index a8353546c69..ef50daba540 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -116,8 +116,16 @@ void falco_outputs::add_output(falco::outputs::config oc) throw falco_exception("Output not supported: " + oc.name); } - oo->init(oc, m_buffered, m_hostname, m_json_output); - m_outputs.push_back(oo); + std::string init_err; + if (oo->init(oc, m_buffered, m_hostname, m_json_output, init_err)) + { + m_outputs.push_back(oo); + } + else + { + falco_logger::log(LOG_ERR, "Failed to init output: " + init_err); + delete(oo); + } } void falco_outputs::handle_event(gen_event *evt, std::string &rule, std::string &source, diff --git a/userspace/falco/outputs.h b/userspace/falco/outputs.h index 1155c8b1898..0ad01d85dae 100644 --- a/userspace/falco/outputs.h +++ b/userspace/falco/outputs.h @@ -62,14 +62,17 @@ struct message class abstract_output { public: - virtual ~abstract_output() {} + virtual ~abstract_output() = default; - void init(const config& oc, bool buffered, const std::string& hostname, bool json_output) + virtual bool init(const config& oc, bool buffered, const std::string& hostname, bool json_output, std::string &err) { m_oc = oc; m_buffered = buffered; m_hostname = hostname; m_json_output = json_output; + + err = ""; + return true; } // Return the output's name as per its configuration. diff --git a/userspace/falco/outputs_http.cpp b/userspace/falco/outputs_http.cpp index 00f784a0558..a3962250cbf 100644 --- a/userspace/falco/outputs_http.cpp +++ b/userspace/falco/outputs_http.cpp @@ -18,104 +18,105 @@ limitations under the License. #include "logger.h" #include "banned.h" // This raises a compilation error when certain functions are used -void falco::outputs::output_http::output(const message *msg) +#define CHECK_RES(fn) res = res == CURLE_OK ? fn : res + +static size_t noop_write_callback(void *contents, size_t size, size_t nmemb, void *userp) +{ + // We don't want to echo anything. Just return size of bytes ignored + return size * nmemb; +} + +bool falco::outputs::output_http::init(const config& oc, bool buffered, const std::string& hostname, bool json_output, std::string &err) { - CURL *curl = NULL; + falco::outputs::abstract_output::init(oc, buffered, hostname, json_output, err); + + m_curl = nullptr; + m_http_headers = nullptr; CURLcode res = CURLE_FAILED_INIT; - struct curl_slist *slist1; - slist1 = NULL; - curl = curl_easy_init(); - if(curl) + m_curl = curl_easy_init(); + if(!m_curl) { - if (m_json_output) - { - slist1 = curl_slist_append(slist1, "Content-Type: application/json"); - } else { - slist1 = curl_slist_append(slist1, "Content-Type: text/plain"); - } - res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1); - - if(res == CURLE_OK) - { - // if the URL is quoted the quotes should be removed to satisfy libcurl expected format - std::string unquotedUrl = m_oc.options["url"]; - if (!unquotedUrl.empty() && ( - (unquotedUrl.front() == '\"' && unquotedUrl.back() == '\"') || - (unquotedUrl.front() == '\'' && unquotedUrl.back() == '\'') - )) - { - unquotedUrl = libsinsp::filter::unescape_str(unquotedUrl); - } - res = curl_easy_setopt(curl, CURLOPT_URL, unquotedUrl.c_str()); - } - - if(res == CURLE_OK) - { - res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg->msg.c_str()); - } - - if(res == CURLE_OK) - { - res = curl_easy_setopt(curl, CURLOPT_USERAGENT, m_oc.options["user_agent"].c_str()); - } - - if(res == CURLE_OK) - { - res = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1L); - } - - if(res == CURLE_OK) - { - if(m_oc.options["insecure"] == std::string("true")) - { - res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - - if(res == CURLE_OK) - { - res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); - } - } - } - - if(res == CURLE_OK) - { - if(m_oc.options["mtls"] == std::string("true")) - { - res = curl_easy_setopt(curl, CURLOPT_SSLCERT, m_oc.options["client_cert"].c_str()); - - if(res == CURLE_OK) - { - res = curl_easy_setopt(curl, CURLOPT_SSLKEY, m_oc.options["client_key"].c_str()); - } - } - } - - if(res == CURLE_OK) - { - if (!m_oc.options["ca_cert"].empty()) - { - res = curl_easy_setopt(curl, CURLOPT_CAINFO, m_oc.options["ca_cert"].c_str()); - }else if(!m_oc.options["ca_bundle"].empty()) - { - res = curl_easy_setopt(curl, CURLOPT_CAINFO, m_oc.options["ca_bundle"].c_str()); - }else{ - res = curl_easy_setopt(curl, CURLOPT_CAPATH, m_oc.options["ca_path"].c_str()); - } - } - - if(res == CURLE_OK) - { - res = curl_easy_perform(curl); - } - - if(res != CURLE_OK) - { - falco_logger::log(LOG_ERR, "libcurl error: " + std::string(curl_easy_strerror(res))); - } - curl_easy_cleanup(curl); - curl = NULL; - curl_slist_free_all(slist1); - slist1 = NULL; + falco_logger::log(LOG_ERR, "libcurl failed to initialize the handle: " + std::string(curl_easy_strerror(res))); + return false; } + if(m_json_output) + { + m_http_headers = curl_slist_append(m_http_headers, "Content-Type: application/json"); + } + else + { + m_http_headers = curl_slist_append(m_http_headers, "Content-Type: text/plain"); + } + 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 + std::string unquotedUrl = m_oc.options["url"]; + if (!unquotedUrl.empty() && ( + (unquotedUrl.front() == '\"' && unquotedUrl.back() == '\"') || + (unquotedUrl.front() == '\'' && unquotedUrl.back() == '\'') + )) + { + unquotedUrl = libsinsp::filter::unescape_str(unquotedUrl); + } + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_URL, unquotedUrl.c_str())); + + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_USERAGENT, m_oc.options["user_agent"].c_str())); + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, -1L)); + + if(m_oc.options["insecure"] == std::string("true")) + { + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L)); + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0L)); + } + + if(m_oc.options["mtls"] == std::string("true")) + { + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_SSLCERT, m_oc.options["client_cert"].c_str())); + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_SSLKEY, m_oc.options["client_key"].c_str())); + } + + if (!m_oc.options["ca_cert"].empty()) + { + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_CAINFO, m_oc.options["ca_cert"].c_str())); + } + else if(!m_oc.options["ca_bundle"].empty()) + { + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_CAINFO, m_oc.options["ca_bundle"].c_str())); + } + else + { + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_CAPATH, m_oc.options["ca_path"].c_str())); + } + + if(m_oc.options["echo"] == std::string("false")) + { + // If echo==true, libcurl defaults to fwrite to stdout, ie: echoing + CHECK_RES(curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, noop_write_callback)); + } + + if(res != CURLE_OK) + { + err = "libcurl error: " + std::string(curl_easy_strerror(res)); + return false; + } + return true; +} + +void falco::outputs::output_http::output(const message *msg) +{ + CURLcode res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, msg->msg.c_str()); + CHECK_RES(curl_easy_perform(m_curl)); + if(res != CURLE_OK) + { + falco_logger::log(LOG_ERR, "libcurl failed to perform call: " + std::string(curl_easy_strerror(res))); + } +} + +void falco::outputs::output_http::cleanup() +{ + curl_easy_cleanup(m_curl); + m_curl = nullptr; + curl_slist_free_all(m_http_headers); + m_http_headers = nullptr; } diff --git a/userspace/falco/outputs_http.h b/userspace/falco/outputs_http.h index d3c9eb667eb..0e715dcbebc 100644 --- a/userspace/falco/outputs_http.h +++ b/userspace/falco/outputs_http.h @@ -25,7 +25,13 @@ namespace outputs class output_http : public abstract_output { - void output(const message *msg); + bool init(const config& oc, bool buffered, const std::string& hostname, bool json_output, std::string &err) override; + void output(const message *msg) override; + void cleanup() override; + +private: + CURL *m_curl; + struct curl_slist *m_http_headers; }; } // namespace outputs