From 359bd6e5932a5d7159d96db82be810d00d30fea3 Mon Sep 17 00:00:00 2001 From: Jason Dellaluce Date: Mon, 13 Nov 2023 13:57:54 +0000 Subject: [PATCH] cleanup(userspace/engine): remove legacy k8saudit implementation Signed-off-by: Jason Dellaluce --- userspace/engine/CMakeLists.txt | 1 - userspace/engine/json_evt.cpp | 1751 ------------------------------- userspace/engine/json_evt.h | 512 --------- 3 files changed, 2264 deletions(-) delete mode 100644 userspace/engine/json_evt.cpp delete mode 100644 userspace/engine/json_evt.h diff --git a/userspace/engine/CMakeLists.txt b/userspace/engine/CMakeLists.txt index 8aecac06738..d2a8a7b843d 100644 --- a/userspace/engine/CMakeLists.txt +++ b/userspace/engine/CMakeLists.txt @@ -16,7 +16,6 @@ set(FALCO_ENGINE_SOURCE_FILES falco_engine.cpp falco_load_result.cpp falco_utils.cpp - json_evt.cpp evttype_index_ruleset.cpp formats.cpp filter_details_resolver.cpp diff --git a/userspace/engine/json_evt.cpp b/userspace/engine/json_evt.cpp deleted file mode 100644 index 5ba6e80d0c2..00000000000 --- a/userspace/engine/json_evt.cpp +++ /dev/null @@ -1,1751 +0,0 @@ -// 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 "utils.h" - -#include "falco_common.h" -#include "json_evt.h" -#include "falco_utils.h" - -using json = nlohmann::json; -using namespace std; - -json_event::json_event() -{ -} - -json_event::~json_event() -{ -} - -void json_event::set_jevt(json &evt, uint64_t ts) -{ - m_jevt = evt; - m_event_ts = ts; -} - -const json &json_event::jevt() -{ - return m_jevt; -} - -uint64_t json_event::get_ts() const -{ - return m_event_ts; -} - -static nlohmann::json::json_pointer k8s_audit_time = "/stageTimestamp"_json_pointer; - -bool falco_k8s_audit::parse_k8s_audit_json(nlohmann::json &j, std::list &evts, bool top) -{ - // Note that nlohmann::basic_json::value can throw nlohmann::basic_json::type_error (302, 306) - try - { - // If the object is an array, call parse_k8s_audit_json again for each item. - if(j.is_array()) - { - if(top) - { - for(auto &item : j) - { - // Note we only handle a single top level array, to - // avoid excessive recursion. - if(! parse_k8s_audit_json(item, evts, false)) - { - return false; - } - } - - return true; - } - else - { - return false; - } - } - - // If the kind is EventList, split it into individual events - if(j.value("kind", "") == "EventList") - { - for(auto &je : j["items"]) - { - evts.emplace_back(); - je["kind"] = "Event"; - - uint64_t ns = 0; - if(!sinsp_utils::parse_iso_8601_utc_string(je.value(k8s_audit_time, ""), ns)) - { - return false; - } - - std::string tmp; - sinsp_utils::ts_to_string(ns, &tmp, false, true); - - evts.back().set_jevt(je, ns); - } - - return true; - } - else if(j.value("kind", "") == "Event") - { - evts.emplace_back(); - uint64_t ns = 0; - if(!sinsp_utils::parse_iso_8601_utc_string(j.value(k8s_audit_time, ""), ns)) - { - return false; - } - - evts.back().set_jevt(j, ns); - return true; - } - else - { - return false; - } - } - catch(exception &e) - { - return false; - } -} - -json_event_value::json_event_value() -{ -} - -json_event_value::~json_event_value() -{ -} - -json_event_value::json_event_value(const std::string &val) -{ - if(parse_as_pair_int64(m_pairval, val)) - { - m_type = JT_INT64_PAIR; - } - else if (parse_as_int64(m_intval, val)) - { - m_type = JT_INT64; - } - else - { - m_stringval = val; - m_type = JT_STRING; - } -} - -json_event_value::json_event_value(int64_t val) : - m_type(JT_INT64), - m_intval(val) -{ -} - -json_event_value::param_type json_event_value::ptype() const -{ - return m_type; -} - -std::string json_event_value::as_string() const -{ - switch(m_type) - { - case JT_STRING: - return m_stringval; - break; - case JT_INT64: - return std::to_string(m_intval); - break; - case JT_INT64_PAIR: - return std::to_string(m_pairval.first) + ":" + std::to_string(m_pairval.second); - break; - default: - return json_event_filter_check::no_value; - } -} - -// This operator allows for somewhat-flexible comparisons between -// numeric values and ranges. A numeric value and range are considered -// "equal" if the value falls within the range. Otherwise, the value -// types must match and be equal. - -bool json_event_value::operator==(const json_event_value &val) const -{ - // A JT_INT64 can be compared to a JT_INT64_PAIR. The value - // must be within the range specified by the pair. - if(m_type == JT_INT64 && - val.m_type == JT_INT64_PAIR) - { - return (m_intval >= val.m_pairval.first && - m_intval <= val.m_pairval.second); - } - else if(m_type == JT_INT64_PAIR && - val.m_type == JT_INT64) - { - return (val.m_intval >= m_pairval.first && - val.m_intval <= m_pairval.second); - } - else if(m_type != val.m_type) - { - return false; - } - - switch(m_type) - { - case JT_STRING: - return (m_stringval == val.m_stringval); - break; - case JT_INT64: - return (m_intval == val.m_intval); - break; - case JT_INT64_PAIR: - return (m_pairval == val.m_pairval); - break; - default: - return false; - } -} - -bool json_event_value::operator!=(const json_event_value &val) const -{ - return !operator==(val); -} - -// This operator allows for somewhat-flexible comparisons between -// numeric values and ranges, or two ranges, and allows for ordering -// values in a set. In practice, a set of values will all have the -// same type, but it will be possible to check for set membership -// between a value and a set of ranges. -// -// A numeric value is less than a range if the value is below the -// lower part of the range. A range A is less than another range B if -// the beginning of A is less than the beginning of B. If A and B -// start at the same value, then the end of the ranges are used -// instead. -// -// For other types, the values are simply compared and for mixed -// types, the event types are considered. -// -bool json_event_value::operator<(const json_event_value &val) const -{ - if(m_type == JT_INT64 && - val.m_type == JT_INT64_PAIR) - { - return (m_intval < val.m_pairval.first); - } - else if(m_type == JT_INT64_PAIR && - val.m_type == JT_INT64) - { - return (m_pairval.second < val.m_intval); - } - else if(m_type != val.m_type) - { - return (m_type < val.m_type); - } - - switch(m_type) - { - case JT_STRING: - return (m_stringval < val.m_stringval); - break; - case JT_INT64: - return (m_intval < val.m_intval); - break; - case JT_INT64_PAIR: - if(m_pairval.first != val.m_pairval.first) - { - return (m_pairval.first < val.m_pairval.first); - } - else - { - return (m_pairval.second < val.m_pairval.second); - } - break; - default: - return false; - } -} - -// See operator< for details. The only thing that changes is the -// comparisons for numeric value and range, or two ranges. -bool json_event_value::operator>(const json_event_value &val) const -{ - // This shouldn't be called when the types differ, but just in - // case, use m_type for initial ordering. - if(m_type != val.m_type) - { - return (m_type < val.m_type); - } - - switch(m_type) - { - case JT_STRING: - return (m_stringval > val.m_stringval); - break; - case JT_INT64: - return (m_intval > val.m_intval); - break; - case JT_INT64_PAIR: - if(m_pairval.first != val.m_pairval.first) - { - return (m_pairval.first > val.m_pairval.first); - } - else - { - return (m_pairval.second > val.m_pairval.second); - } - break; - default: - return false; - } -} - -bool json_event_value::startswith(const json_event_value &val) const -{ - std::string str = as_string(); - std::string valstr = val.as_string(); - - return (str.compare(0, valstr.size(), valstr) == 0); -} - -bool json_event_value::contains(const json_event_value &val) const -{ - std::string str = as_string(); - std::string valstr = val.as_string(); - - return (str.find(valstr) != string::npos); -} - -bool json_event_value::parse_as_pair_int64(std::pair &pairval, const std::string &val) -{ - size_t pos = val.find_first_of(':'); - if(pos != std::string::npos && - json_event_value::parse_as_int64(pairval.first, val.substr(0, pos)) && - json_event_value::parse_as_int64(pairval.second, val.substr(pos+1))) - { - return true; - } - - return false; -} - -bool json_event_value::parse_as_int64(int64_t &intval, const std::string &val) -{ - try { - std::string::size_type ptr; - - intval = std::stoll(val, &ptr); - - if(ptr != val.length()) - { - return false; - } - } - catch(std::out_of_range &) - { - return false; - } - catch (std::invalid_argument &) - { - return false; - } - - return true; -} - -std::string json_event_filter_check::no_value = ""; -std::vector json_event_filter_check::s_index_mode_strs = {"IDX_REQUIRED", "IDX_ALLOWED", "IDX_NONE"}; -std::vector json_event_filter_check::s_index_type_strs = {"IDX_KEY", "IDX_NUMERIC"}; - -bool json_event_filter_check::def_extract(const nlohmann::json &root, - const std::list &ptrs, - std::list::iterator it) -{ - if(it == ptrs.end()) - { - add_extracted_value(json_as_string(root)); - return true; - } - - try { - const json &j = root.at(*it); - - if(j.is_array()) - { - for(auto &item : j) - { - if(!def_extract(item, ptrs, std::next(it, 1))) - { - add_extracted_value(no_value); - } - } - } - else - { - add_extracted_value(json_as_string(j)); - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -std::string json_event_filter_check::json_as_string(const json &j) -{ - if(j.type() == json::value_t::string) - { - return j; - } - else - { - return j.dump(); - } -} - -json_event_filter_check::field_info::field_info(): - m_idx_mode(IDX_NONE), - m_idx_type(IDX_NUMERIC), - m_uses_paths(false) -{ -} - -json_event_filter_check::field_info::field_info(std::string name, - std::string desc): - m_name(name), - m_desc(desc), - m_idx_mode(IDX_NONE), - m_idx_type(IDX_NUMERIC), - m_uses_paths(false) -{ -} - -json_event_filter_check::field_info::field_info(std::string name, - std::string desc, - index_mode mode): - m_name(name), - m_desc(desc), - m_idx_mode(mode), - m_idx_type(IDX_NUMERIC), - m_uses_paths(false) -{ -} - -json_event_filter_check::field_info::field_info(std::string name, - std::string desc, - index_mode mode, - index_type itype): - m_name(name), - m_desc(desc), - m_idx_mode(mode), - m_idx_type(itype), - m_uses_paths(false) -{ -} - -json_event_filter_check::field_info::field_info(std::string name, - std::string desc, - index_mode mode, - index_type itype, - bool uses_paths): - m_name(name), - m_desc(desc), - m_idx_mode(mode), - m_idx_type(itype), - m_uses_paths(uses_paths) -{ -} - -json_event_filter_check::field_info::~field_info() -{ -} - -json_event_filter_check::alias::alias() -{ -} - -json_event_filter_check::alias::alias(std::list ptrs) : - m_jptrs(ptrs) -{ -} - -json_event_filter_check::alias::alias(extract_t extract) : - m_extract(extract) -{ -} - -json_event_filter_check::alias::~alias() -{ -} - -json_event_filter_check::json_event_filter_check() -{ -} - -json_event_filter_check::~json_event_filter_check() -{ -} - -int32_t json_event_filter_check::parse_field_name(const char *str, bool alloc_state, bool needed_for_filtering) -{ - // Look for the longest match amongst the aliases. str is not - // necessarily terminated on a filtercheck boundary. - size_t match_len = 0; - - size_t idx_len = 0; - - for(const auto &info : get_info().m_fields) - { - auto iter = get_aliases().find(info.m_name); - if( iter == get_aliases().end()) - { - throw falco_exception("Could not find alias for field name " + info.m_name); - } - - m_uses_paths = info.m_uses_paths; - - auto &al = iter->second; - - // What follows the match must not be alphanumeric or a dot - if(strncmp(info.m_name.c_str(), str, info.m_name.size()) == 0 && - !isalnum((int)str[info.m_name.size()]) && - str[info.m_name.size()] != '.' && - info.m_name.size() > match_len) - { - m_jptrs = al.m_jptrs; - m_field = info.m_name; - - if(al.m_extract) - { - m_extract = al.m_extract; - } - match_len = info.m_name.size(); - - const char *start = str + m_field.size(); - - // Check for an optional index - if(*start == '[') - { - start++; - const char *end = strchr(start, ']'); - - if(end != NULL) - { - m_idx = string(start, end - start); - } - - idx_len = (end - start + 2); - } - - if(m_idx.empty() && info.m_idx_mode == IDX_REQUIRED) - { - throw falco_exception(string("When parsing filtercheck ") + string(str) + string(": ") + m_field + string(" requires an index but none provided")); - } - - if(!m_idx.empty() && info.m_idx_mode == IDX_NONE) - { - throw falco_exception(string("When parsing filtercheck ") + string(str) + string(": ") + m_field + string(" forbids an index but one provided")); - } - - if(!m_idx.empty() && - info.m_idx_type == IDX_NUMERIC && - m_idx.find_first_not_of("0123456789") != string::npos) - { - throw falco_exception(string("When parsing filtercheck ") + string(str) + string(": ") + m_field + string(" requires a numeric index")); - } - } - } - - return match_len + idx_len; -} - -void json_event_filter_check::add_filter_value(const char *str, uint32_t len, uint32_t i) -{ - m_values.insert(string(str)); -} - -const json_event_filter_check::values_t &json_event_filter_check::extracted_values() -{ - return m_evalues.first; -} - -bool json_event_filter_check::compare(gen_event *evt) -{ - auto jevt = (json_event *) evt; - std::vector values; - if (!extract(jevt, values)) - { - return false; - } - auto evalues = (const extracted_values_t *) values[0].ptr; - - values_set_t setvals; - switch(m_cmpop) - { - case CO_EQ: - return evalues->second == m_values; - case CO_NE: - return evalues->second != m_values; - case CO_STARTSWITH: - return (evalues->first.size() == 1 && - m_values.size() == 1 && - evalues->first.at(0).startswith(*(m_values.begin()))); - case CO_CONTAINS: - return (evalues->first.size() == 1 && - m_values.size() == 1 && - evalues->first.at(0).contains(*(m_values.begin()))); - case CO_IN: - for(auto &item : evalues->second) - { - if(m_values.find(item) == m_values.end()) - { - return false; - } - } - return true; - case CO_PMATCH: - for(auto &item : evalues->second) - { - if(item.as_string() != no_value) - { - if(!m_prefix_search.match(item.as_string().c_str())) - { - return false; - } - } - } - return true; - case CO_INTERSECTS: - std::set_intersection(evalues->second.begin(), evalues->second.end(), - m_values.begin(), m_values.end(), - std::inserter(setvals, setvals.begin())); - return (!setvals.empty()); - case CO_LT: - return (evalues->first.size() == 1 && - m_values.size() == 1 && - evalues->first.at(0).ptype() == m_values.begin()->ptype() && - evalues->first.at(0) < *(m_values.begin())); - case CO_LE: - return (evalues->first.size() == 1 && - m_values.size() == 1 && - evalues->first.at(0).ptype() == m_values.begin()->ptype() && - (evalues->first.at(0) < *(m_values.begin()) || - evalues->first.at(0) == *(m_values.begin()))); - case CO_GT: - return (evalues->first.size() == 1 && - m_values.size() == 1 && - evalues->first.at(0).ptype() == m_values.begin()->ptype() && - evalues->first.at(0) > *(m_values.begin())); - case CO_GE: - return (evalues->first.size() == 1 && - m_values.size() == 1 && - evalues->first.at(0).ptype() == m_values.begin()->ptype() && - (evalues->first.at(0) > *(m_values.begin()) || - evalues->first.at(0) == *(m_values.begin()))); - case CO_EXISTS: - return (evalues->first.size() == 1 && - (evalues->first.at(0) != json_event_filter_check::no_value)); - default: - throw falco_exception("filter error: unsupported comparison operator"); - } -} - -const std::string &json_event_filter_check::field() -{ - return m_field; -} - -const std::string &json_event_filter_check::idx() -{ - return m_idx; -} - -size_t json_event_filter_check::parsed_size() -{ - if(m_idx.empty()) - { - return m_field.size(); - } - else - { - return m_field.size() + m_idx.size() + 2; - } -} - -void json_event_filter_check::add_extracted_value(const std::string &str) -{ - m_evalues.first.emplace_back(json_event_value(str)); - m_evalues.second.emplace(json_event_value(str)); - - if(m_uses_paths) - { - m_prefix_search.add_search_path(str); - } -} - -void json_event_filter_check::add_extracted_value_num(int64_t val) -{ - m_evalues.first.emplace_back(json_event_value(val)); - m_evalues.second.emplace(json_event_value(val)); -} - -bool json_event_filter_check::extract(gen_event *evt, std::vector& values, bool sanitize_strings) -{ - m_evalues.first.clear(); - m_evalues.second.clear(); - - if (!extract_values((json_event *) evt)) - { - m_evalues.first.clear(); - m_evalues.second.clear(); - add_extracted_value(no_value); - } - values.push_back({(uint8_t *)&m_evalues, sizeof(m_evalues)}); - return true; -} - -bool json_event_filter_check::extract_values(json_event *jevt) -{ - try - { - if(m_extract) - { - if(!m_extract(jevt->jevt(), *this)) - { - return false; - } - } - else - { - if (!def_extract(jevt->jevt(), m_jptrs, m_jptrs.begin())) - { - return false; - } - - if(! m_idx.empty()) - { - // The default only knows how to index by numeric indices - try { - std::string::size_type ptr; - std::string::size_type idx_num = std::stoll(m_idx, &ptr); - - if(ptr != m_idx.length()) - { - return false; - } - - if(idx_num >= m_evalues.first.size()) - { - return false; - } - - values_t new_values; - new_values.push_back(m_evalues.first.at(idx_num)); - m_evalues.first = new_values; - } - catch (std::invalid_argument &e) - { - return false; - } - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -std::string jevt_filter_check::s_jevt_time_field = "jevt.time"; -std::string jevt_filter_check::s_jevt_time_iso_8601_field = "jevt.time.iso8601"; -std::string jevt_filter_check::s_jevt_rawtime_field = "jevt.rawtime"; -std::string jevt_filter_check::s_jevt_value_field = "jevt.value"; -std::string jevt_filter_check::s_jevt_obj_field = "jevt.obj"; - -const jevt_filter_check::check_info &jevt_filter_check::get_info() const -{ - static const check_info info = {"jevt", - "generic ways to access json events", - "", - {{s_jevt_time_field, "json event timestamp as a string that includes the nanosecond part"}, - {s_jevt_time_iso_8601_field, "json event timestamp in ISO 8601 format, including nanoseconds and time zone offset (in UTC)"}, - {s_jevt_rawtime_field, "absolute event timestamp, i.e. nanoseconds from epoch."}, - {s_jevt_value_field, "General way to access single property from json object. The syntax is []. The property is returned as a string", IDX_REQUIRED, IDX_KEY}, - {s_jevt_obj_field, "The entire json object, stringified"}}}; - return info; -} - -jevt_filter_check::jevt_filter_check() -{ -} - -jevt_filter_check::~jevt_filter_check() -{ -} - -int32_t jevt_filter_check::parse_field_name(const char *str, bool alloc_state, bool needed_for_filtering) -{ - if(strncmp(s_jevt_time_iso_8601_field.c_str(), str, s_jevt_time_iso_8601_field.size()) == 0) - { - m_field = s_jevt_time_iso_8601_field; - return s_jevt_time_iso_8601_field.size(); - } - - if(strncmp(s_jevt_time_field.c_str(), str, s_jevt_time_field.size()) == 0) - { - m_field = s_jevt_time_field; - return s_jevt_time_field.size(); - } - - if(strncmp(s_jevt_rawtime_field.c_str(), str, s_jevt_rawtime_field.size()) == 0) - { - m_field = s_jevt_rawtime_field; - return s_jevt_rawtime_field.size(); - } - - if(strncmp(s_jevt_obj_field.c_str(), str, s_jevt_obj_field.size()) == 0) - { - m_field = s_jevt_obj_field; - return s_jevt_obj_field.size(); - } - - if(strncmp(s_jevt_value_field.c_str(), str, s_jevt_value_field.size()) == 0) - { - const char *end; - - // What follows must be [] - if(*(str + s_jevt_value_field.size()) != '[' || - ((end = strchr(str + 1, ']')) == NULL)) - - { - throw falco_exception(string("Could not parse filtercheck field \"") + str + "\". Did not have expected format with 'jevt.value[]'"); - } - - try - { - m_idx = string(str + (s_jevt_value_field.size() + 1), (end - str - (s_jevt_value_field.size() + 1))); - m_idx_ptr = json::json_pointer(m_idx); - } - catch(json::parse_error &e) - { - throw falco_exception(string("Could not parse filtercheck field \"") + str + "\". Invalid json selector (" + e.what() + ")"); - } - - m_field = s_jevt_value_field; - - // The +1 accounts for the closing ']' - return (end - str + 1); - } - - return 0; -} - -bool jevt_filter_check::extract_values(json_event *jevt) -{ - std::string tstr; - - if(m_field == s_jevt_rawtime_field) - { - tstr = to_string(jevt->get_ts()); - } - else if(m_field == s_jevt_time_field) - { - sinsp_utils::ts_to_string(jevt->get_ts(), &tstr, false, true); - } - else if(m_field == s_jevt_time_iso_8601_field) - { - sinsp_utils::ts_to_iso_8601(jevt->get_ts(), &tstr); - } - else if(m_field == s_jevt_obj_field) - { - tstr = jevt->jevt().dump(); - } - else if (m_field == s_jevt_value_field) - { - try { - const json &j = jevt->jevt().at(m_idx_ptr); - tstr = json_as_string(j); - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - } - else - { - return false; - } - - add_extracted_value(tstr); - return true; -} - -json_event_filter_check *jevt_filter_check::allocate_new() -{ - jevt_filter_check *chk = new jevt_filter_check(); - - return (json_event_filter_check *)chk; -} - -bool k8s_audit_filter_check::extract_images(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer containers_ptr = "/requestObject/spec/containers"_json_pointer; - - try - { - const json &containers = j.at(containers_ptr); - - for(auto &container : containers) - { - std::string image = container.at("image"); - - // If the filtercheck ends with .repository, we want only the - // repo name from the image. - std::string suffix = ".repository"; - if(suffix.size() <= jchk.field().size() && - std::equal(suffix.rbegin(), suffix.rend(), jchk.field().rbegin())) - { - std::string hostname, port, name, tag, digest; - - sinsp_utils::split_container_image(image, - hostname, - port, - name, - tag, - digest, - false); - jchk.add_extracted_value(name); - } - else - { - jchk.add_extracted_value(image); - } - - if(jchk.field() == "ka.req.container.image" || - jchk.field() == "ka.req.container.image.repository") - { - // Stop at the first container - break; - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -bool k8s_audit_filter_check::extract_query_param(const nlohmann::json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer request_uri_ptr = "/requestURI"_json_pointer; - - string uri; - std::vector uri_parts, query_parts; - - try { - uri = j.at(request_uri_ptr); - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - uri_parts = sinsp_split(uri, '?'); - - if(uri_parts.size() != 2) - { - return false; - } - - query_parts = sinsp_split(uri_parts[1], '&'); - - for(auto &part : query_parts) - { - std::vector param_parts = sinsp_split(part, '='); - - if(param_parts.size() == 2 && falco::utils::decode_uri(param_parts[0], true) == jchk.idx()) - { - jchk.add_extracted_value(param_parts[1]); - return true; - } - } - - return false; -} - - -bool k8s_audit_filter_check::extract_rule_attrs(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer rules_ptr = "/requestObject/rules"_json_pointer; - - // Use the suffix of the field to determine which property to - // select from each object. - std::string prop = jchk.field().substr(jchk.field().find_last_of(".") + 1); - - try - { - const json &rules = j.at(rules_ptr); - - for (auto &rule : rules) - { - if(rule.find(prop) != rule.end()) - { - for (auto &item : rule.at(prop)) - { - jchk.add_extracted_value(json_as_string(item)); - } - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -bool k8s_audit_filter_check::check_volumes_hostpath(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer volumes_ptr = "/requestObject/spec/volumes"_json_pointer; - - try - { - const nlohmann::json &vols = j.at(volumes_ptr); - - for(auto &vol : vols) - { - auto it = vol.find("hostPath"); - if(it != vol.end()) - { - // If the index key ends with a *, do - // a prefix match. Otherwise, compare - // for equality. - - if(jchk.idx().back() == '*') - { - if(jchk.idx().compare(0, jchk.idx().size() - 1, *it) == 0) - { - jchk.add_extracted_value(string("true")); - } - } - else - { - if(jchk.idx() == *it) - { - jchk.add_extracted_value(string("true")); - } - } - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - jchk.add_extracted_value(string("false")); - - return true; -} - -bool k8s_audit_filter_check::extract_volume_types(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer volumes_ptr = "/requestObject/spec/volumes"_json_pointer; - - try { - const nlohmann::json &vols = j.at(volumes_ptr); - - for(auto &vol : vols) - { - for (auto it = vol.begin(); it != vol.end(); ++it) - { - // Any key other than "name" represents a volume type - if(it.key() != "name") - { - jchk.add_extracted_value(it.key()); - } - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -bool k8s_audit_filter_check::extract_host_port(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer containers_ptr = "/requestObject/spec/containers"_json_pointer; - - try { - const json &containers = j.at(containers_ptr); - - for(auto &container : containers) - { - if(container.find("ports") == container.end()) - { - continue; - } - - nlohmann::json ports = container["ports"]; - - for(auto &cport : ports) - { - if(cport.find("hostPort") != cport.end()) - { - jchk.add_extracted_value(json_as_string(cport.at("hostPort"))); - } - else if (cport.find("containerPort") != cport.end()) - { - // When hostNetwork is true, this will match the host port. - jchk.add_extracted_value(json_as_string(cport.at("containerPort"))); - } - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -bool k8s_audit_filter_check::extract_effective_run_as(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer spec_ptr = "/requestObject/spec"_json_pointer; - static json::json_pointer containers_ptr = "/containers"_json_pointer; - static json::json_pointer run_as_user_ptr = "/securityContext/runAsUser"_json_pointer; - static json::json_pointer run_as_group_ptr = "/securityContext/runAsGroup"_json_pointer; - - try { - const json &spec = j.at(spec_ptr); - - int64_t pod_id; - - if(jchk.field() == "ka.req.pod.containers.eff_run_as_user") - { - pod_id = spec.value(run_as_user_ptr, 0); - } - else - { - pod_id = spec.value(run_as_group_ptr, 0); - } - - const json &containers = spec.at(containers_ptr); - - for(auto container : containers) - { - int64_t container_id; - - if(jchk.field() == "ka.req.pod.containers.eff_run_as_user") - { - container_id = container.value(run_as_user_ptr, pod_id); - } - else - { - container_id = container.value(run_as_group_ptr, pod_id); - } - - jchk.add_extracted_value_num(container_id); - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - return true; -} - -bool k8s_audit_filter_check::always_return_na(const json &j, - json_event_filter_check &jchk) -{ - jchk.add_extracted_value(string("true")); - - return true; -} - -bool k8s_audit_filter_check::extract_any_privileged(const json &j, - json_event_filter_check &jchk) -{ - static json::json_pointer containers_ptr = "/requestObject/spec/containers"_json_pointer; - static json::json_pointer privileged_ptr = "/securityContext/privileged"_json_pointer; - - try - { - const json &containers = j.at(containers_ptr); - - for(auto &container : containers) - { - bool privileged = false; - try - { - privileged = container.at(privileged_ptr); - } - catch(json::out_of_range &e) - { - // No-op - } - catch(json::type_error &e) - { - return false; - } - - if(privileged) - { - jchk.add_extracted_value(string("true")); - return true; - } - } - } - catch(json::out_of_range &e) - { - return false; - } - catch(json::type_error &e) - { - return false; - } - - jchk.add_extracted_value(string("false")); - return true; -} - -const json_event_filter_check::check_info &k8s_audit_filter_check::get_info() const -{ - static const json_event_filter_check::check_info - info = {"ka", - "Access K8s Audit Log Events", - "Fields with an IDX_ALLOWED annotation can be indexed (e.g. ka.req.containers.image[k] returns the image for the kth container). The index is optional--without any index the field returns values for all items. The index must be numeric with an IDX_NUMERIC annotation, and can be any string with an IDX_KEY annotation. Fields with an IDX_REQUIRED annotation require an index.", - {{"ka.auditid", "The unique id of the audit event"}, - {"ka.stage", "Stage of the request (e.g. RequestReceived, ResponseComplete, etc.)"}, - {"ka.auth.decision", "The authorization decision"}, - {"ka.auth.reason", "The authorization reason"}, - {"ka.user.name", "The user name performing the request"}, - {"ka.user.groups", "The groups to which the user belongs"}, - {"ka.impuser.name", "The impersonated user name"}, - {"ka.verb", "The action being performed"}, - {"ka.uri", "The request URI as sent from client to server"}, - {"ka.uri.param", "The value of a given query parameter in the uri (e.g. when uri=/foo?key=val, ka.uri.param[key] is val).", IDX_REQUIRED, IDX_KEY}, - {"ka.target.name", "The target object name"}, - {"ka.target.namespace", "The target object namespace"}, - {"ka.target.resource", "The target object resource"}, - {"ka.target.subresource", "The target object subresource"}, - {"ka.req.binding.subjects", "When the request object refers to a cluster role binding, the subject (e.g. account/users) being linked by the binding"}, - {"ka.req.binding.role", "When the request object refers to a cluster role binding, the role being linked by the binding"}, - {"ka.req.binding.subject.has_name", "Deprecated, always returns \"N/A\". Only provided for backwards compatibility", IDX_REQUIRED, IDX_KEY}, - {"ka.req.configmap.name", "If the request object refers to a configmap, the configmap name"}, - {"ka.req.configmap.obj", "If the request object refers to a configmap, the entire configmap object"}, - {"ka.req.pod.containers.image", "When the request object refers to a pod, the container's images.", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.container.image", "Deprecated by ka.req.pod.containers.image. Returns the image of the first container only"}, - {"ka.req.pod.containers.image.repository", "The same as req.container.image, but only the repository part (e.g. falcosecurity/falco).", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.container.image.repository", "Deprecated by ka.req.pod.containers.image.repository. Returns the repository of the first container only"}, - {"ka.req.pod.host_ipc", "When the request object refers to a pod, the value of the hostIPC flag."}, - {"ka.req.pod.host_network", "When the request object refers to a pod, the value of the hostNetwork flag."}, - {"ka.req.container.host_network", "Deprecated alias for ka.req.pod.host_network"}, - {"ka.req.pod.host_pid", "When the request object refers to a pod, the value of the hostPID flag."}, - {"ka.req.pod.containers.host_port", "When the request object refers to a pod, all container's hostPort values.", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.containers.privileged", "When the request object refers to a pod, the value of the privileged flag for all containers.", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.container.privileged", "Deprecated by ka.req.pod.containers.privileged. Returns true if any container has privileged=true"}, - {"ka.req.pod.containers.allow_privilege_escalation", "When the request object refers to a pod, the value of the allowPrivilegeEscalation flag for all containers", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.containers.read_only_fs", "When the request object refers to a pod, the value of the readOnlyRootFilesystem flag for all containers", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.run_as_user", "When the request object refers to a pod, the runAsUser uid specified in the security context for the pod. See ....containers.run_as_user for the runAsUser for individual containers"}, - {"ka.req.pod.containers.run_as_user", "When the request object refers to a pod, the runAsUser uid for all containers", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.containers.eff_run_as_user", "When the request object refers to a pod, the initial uid that will be used for all containers. This combines information from both the pod and container security contexts and uses 0 if no uid is specified", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.run_as_group", "When the request object refers to a pod, the runAsGroup gid specified in the security context for the pod. See ....containers.run_as_group for the runAsGroup for individual containers"}, - {"ka.req.pod.containers.run_as_group", "When the request object refers to a pod, the runAsGroup gid for all containers", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.containers.eff_run_as_group", "When the request object refers to a pod, the initial gid that will be used for all containers. This combines information from both the pod and container security contexts and uses 0 if no gid is specified", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.containers.proc_mount", "When the request object refers to a pod, the procMount types for all containers", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.role.rules", "When the request object refers to a role/cluster role, the rules associated with the role"}, - {"ka.req.role.rules.apiGroups", "When the request object refers to a role/cluster role, the api groups associated with the role's rules", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.role.rules.nonResourceURLs", "When the request object refers to a role/cluster role, the non resource urls associated with the role's rules", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.role.rules.verbs", "When the request object refers to a role/cluster role, the verbs associated with the role's rules", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.role.rules.resources", "When the request object refers to a role/cluster role, the resources associated with the role's rules", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.fs_group", "When the request object refers to a pod, the fsGroup gid specified by the security context."}, - {"ka.req.pod.supplemental_groups", "When the request object refers to a pod, the supplementalGroup gids specified by the security context."}, - {"ka.req.pod.containers.add_capabilities", "When the request object refers to a pod, all capabilities to add when running the container.", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.service.type", "When the request object refers to a service, the service type"}, - {"ka.req.service.ports", "When the request object refers to a service, the service's ports", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.volumes.hostpath", "When the request object refers to a pod, all hostPath paths specified for all volumes", IDX_ALLOWED, IDX_NUMERIC, true}, - {"ka.req.volume.hostpath", "Deprecated by ka.req.pod.volumes.hostpath. Return true if the provided (host) path prefix is used by any volume", IDX_ALLOWED, IDX_KEY}, - {"ka.req.pod.volumes.flexvolume_driver", "When the request object refers to a pod, all flexvolume drivers specified for all volumes", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.req.pod.volumes.volume_type", "When the request object refers to a pod, all volume types for all volumes", IDX_ALLOWED, IDX_NUMERIC}, - {"ka.resp.name", "The response object name"}, - {"ka.response.code", "The response code"}, - {"ka.response.reason", "The response reason (usually present only for failures)"}, - {"ka.useragent", "The useragent of the client who made the request to the apiserver"}}}; - return info; - -} - -const std::unordered_map &k8s_audit_filter_check::get_aliases() const -{ - static const std::unordered_map - aliases = { - {"ka.auditid", {{"/auditID"_json_pointer}}}, - {"ka.stage", {{"/stage"_json_pointer}}}, - {"ka.auth.decision", {{"/annotations/authorization.k8s.io~1decision"_json_pointer}}}, - {"ka.auth.reason", {{"/annotations/authorization.k8s.io~1reason"_json_pointer}}}, - {"ka.user.name", {{"/user/username"_json_pointer}}}, - {"ka.user.groups", {{"/user/groups"_json_pointer}}}, - {"ka.impuser.name", {{"/impersonatedUser/username"_json_pointer}}}, - {"ka.verb", {{"/verb"_json_pointer}}}, - {"ka.uri", {{"/requestURI"_json_pointer}}}, - {"ka.uri.param", {extract_query_param}}, - {"ka.target.name", {{"/objectRef/name"_json_pointer}}}, - {"ka.target.namespace", {{"/objectRef/namespace"_json_pointer}}}, - {"ka.target.resource", {{"/objectRef/resource"_json_pointer}}}, - {"ka.target.subresource", {{"/objectRef/subresource"_json_pointer}}}, - {"ka.req.binding.subjects", {{"/requestObject/subjects"_json_pointer}}}, - {"ka.req.binding.role", {{"/requestObject/roleRef/name"_json_pointer}}}, - {"ka.req.binding.subject.has_name", {always_return_na}}, - {"ka.req.configmap.name", {{"/objectRef/name"_json_pointer}}}, - {"ka.req.configmap.obj", {{"/requestObject/data"_json_pointer}}}, - {"ka.req.pod.containers.image", {extract_images}}, - {"ka.req.container.image", {extract_images}}, - {"ka.req.pod.containers.image.repository", {extract_images}}, - {"ka.req.container.image.repository", {extract_images}}, - {"ka.req.pod.host_ipc", {{"/requestObject/spec/hostIPC"_json_pointer}}}, - {"ka.req.pod.host_network", {{"/requestObject/spec/hostNetwork"_json_pointer}}}, - {"ka.req.container.host_network", {{"/requestObject/spec/hostNetwork"_json_pointer}}}, - {"ka.req.pod.host_pid", {{"/requestObject/spec/hostPID"_json_pointer}}}, - {"ka.req.pod.containers.host_port", {extract_host_port}}, - {"ka.req.pod.containers.privileged", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/privileged"_json_pointer}}}, - {"ka.req.container.privileged", {extract_any_privileged}}, - {"ka.req.pod.containers.allow_privilege_escalation", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/allowPrivilegeEscalation"_json_pointer}}}, - {"ka.req.pod.containers.read_only_fs", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/readOnlyRootFilesystem"_json_pointer}}}, - {"ka.req.pod.run_as_user", {{"/requestObject/spec/securityContext/runAsUser"_json_pointer}}}, - {"ka.req.pod.containers.run_as_user", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/runAsGroup"_json_pointer}}}, - {"ka.req.pod.containers.eff_run_as_user", {extract_effective_run_as}}, - {"ka.req.pod.run_as_group", {{"/requestObject/spec/securityContext/runAsGroup"_json_pointer}}}, - {"ka.req.pod.containers.run_as_group", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/runAsGroup"_json_pointer}}}, - {"ka.req.pod.containers.eff_run_as_group", {extract_effective_run_as}}, - {"ka.req.pod.containers.proc_mount", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/procMount"_json_pointer}}}, - {"ka.req.role.rules", {{"/requestObject/rules"_json_pointer}}}, - {"ka.req.role.rules.apiGroups", {extract_rule_attrs}}, - {"ka.req.role.rules.nonResourceURLs", {extract_rule_attrs}}, - {"ka.req.role.rules.verbs", {extract_rule_attrs}}, - {"ka.req.role.rules.resources", {extract_rule_attrs}}, - {"ka.req.pod.fs_group", {{"/requestObject/spec/securityContext/fsGroup"_json_pointer}}}, - {"ka.req.pod.supplemental_groups", {{"/requestObject/spec/securityContext/supplementalGroups"_json_pointer}}}, - {"ka.req.pod.containers.add_capabilities", {{"/requestObject/spec/containers"_json_pointer, "/securityContext/capabilities/add"_json_pointer}}}, - {"ka.req.service.type", {{"/requestObject/spec/type"_json_pointer}}}, - {"ka.req.service.ports", {{"/requestObject/spec/ports"_json_pointer}}}, - {"ka.req.pod.volumes.hostpath", {{"/requestObject/spec/volumes"_json_pointer, "/hostPath/path"_json_pointer}}}, - {"ka.req.volume.hostpath", {check_volumes_hostpath}}, - {"ka.req.pod.volumes.flexvolume_driver", {{"/requestObject/spec/volumes"_json_pointer, "/flexVolume/driver"_json_pointer}}}, - {"ka.req.pod.volumes.volume_type", {extract_volume_types}}, - {"ka.resp.name", {{"/responseObject/metadata/name"_json_pointer}}}, - {"ka.response.code", {{"/responseStatus/code"_json_pointer}}}, - {"ka.response.reason", {{"/responseStatus/reason"_json_pointer}}}, - {"ka.useragent", {{"/userAgent"_json_pointer}}}}; - return aliases; -} - -k8s_audit_filter_check::k8s_audit_filter_check() -{ -} - -k8s_audit_filter_check::~k8s_audit_filter_check() -{ -} - -json_event_filter_check *k8s_audit_filter_check::allocate_new() -{ - k8s_audit_filter_check *chk = new k8s_audit_filter_check(); - - return (json_event_filter_check *)chk; -} - -json_event_filter::json_event_filter(): sinsp_filter(NULL) -{ -} - -json_event_filter::~json_event_filter() -{ -} - -json_event_filter_factory::json_event_filter_factory() -{ - m_defined_checks.push_back(shared_ptr(new jevt_filter_check())); - m_defined_checks.push_back(shared_ptr(new k8s_audit_filter_check())); - - for(auto &chk : m_defined_checks) - { - m_info.push_back(chk->get_info()); - } -} - -json_event_filter_factory::~json_event_filter_factory() -{ -} - -gen_event_filter *json_event_filter_factory::new_filter() -{ - return new json_event_filter(); -} - -gen_event_filter_check *json_event_filter_factory::new_filtercheck(const char *fldname) -{ - for(auto &chk : m_defined_checks) - { - json_event_filter_check *newchk = chk->allocate_new(); - - int32_t parsed = newchk->parse_field_name(fldname, false, true); - - if(parsed > 0) - { - return newchk; - } - - delete newchk; - } - - return NULL; -} - -std::list json_event_filter_factory::get_fields() -{ - std::list ret; - - // It's not quite copy to the public information, as m_info - // has addl info about indexing. That info is added to the - // description. - - for(auto &chk: m_defined_checks) - { - const json_event_filter_check::check_info &info = chk->get_info(); - gen_event_filter_factory::filter_fieldclass_info cinfo; - - cinfo.name = info.m_name; - cinfo.desc = info.m_desc; - cinfo.shortdesc = info.m_shortdesc; - - for(const auto &field : info.m_fields) - { - gen_event_filter_factory::filter_field_info info; - info.name = field.m_name; - info.desc = field.m_desc; - - // All json fields work on strings - info.data_type = "CHARBUF"; - - switch(field.m_idx_mode) - { - case json_event_filter_check::IDX_REQUIRED: - case json_event_filter_check::IDX_ALLOWED: - info.tags.insert(json_event_filter_check::s_index_mode_strs[field.m_idx_mode]); - break; - case json_event_filter_check::IDX_NONE: - default: - break; - }; - - cinfo.fields.emplace_back(std::move(info)); - } - - ret.emplace_back(std::move(cinfo)); - } - - return ret; -} - -json_event_formatter::json_event_formatter(std::shared_ptr json_factory) - : m_output_format(OF_NORMAL), m_json_factory(json_factory) -{ -} - -json_event_formatter::~json_event_formatter() -{ -} - -void json_event_formatter::set_format(output_format of, const std::string &format) -{ - m_output_format = of; - m_format = format; - parse_format(); -} - -bool json_event_formatter::tostring_withformat(gen_event *gevt, std::string &output, gen_event_formatter::output_format of) -{ - json_event *ev = static_cast(gevt); - if(of == OF_JSON) - { - output = tojson(ev); - return true; - } - else - { - std::list> resolved; - - resolve_format(ev, resolved); - - output = ""; - - for(auto &res : resolved) - { - output += res.second; - } - - return true; - } -} - -bool json_event_formatter::tostring(gen_event *gevt, std::string &output) -{ - return tostring_withformat(gevt, output, m_output_format); -} - -std::string json_event_formatter::tojson(json_event *ev) -{ - nlohmann::json ret; - // todo(leodido, fntlnz) > assign tomap() result to ret (implicit conversion using = operator) - - std::list> resolved; - - resolve_format(ev, resolved); - - for(auto &res : resolved) - { - // Only include the fields and not the raw text blocks. - if(!res.first.empty()) - { - // todo(leodido, fntlnz) > do we want "" rather than empty res.second values? - ret[res.first] = res.second; - } - } - - return ret.dump(); -} - -bool json_event_formatter::get_field_values(gen_event *gevt, std::map &fields) -{ - json_event *ev = static_cast(gevt); - - std::list> res; - - resolve_format(ev, res); - - for(auto &r : res) - { - // Only include the fields and not the raw text blocks. - if(!r.first.empty()) - { - if(r.second.empty()) - { - r.second = ""; - } - fields.insert(r); - } - } - - return true; -} - -void json_event_formatter::parse_format() -{ - string tformat = m_format; - - // Remove any leading '*' if present - if(tformat.front() == '*') - { - tformat.erase(0, 1); - } - - while(tformat.size() > 0) - { - size_t size; - struct fmt_token tok; - - if(tformat.front() == '%') - { - // Skip the % - tformat.erase(0, 1); - json_event_filter_check *chk = (json_event_filter_check *)m_json_factory->new_filtercheck(tformat.c_str()); - - if(!chk) - { - throw falco_exception(string("Could not parse format string \"") + m_format + "\": unknown filtercheck field " + tformat); - } - - size = chk->parsed_size(); - tok.check.reset(chk); - } - else - { - size = tformat.find_first_of("%"); - if(size == string::npos) - { - size = tformat.size(); - } - } - - if(size == 0) - { - // Empty fields are only allowed at the beginning of the string - if(m_tokens.size() > 0) - { - throw falco_exception(string("Could not parse format string \"" + m_format + "\": empty filtercheck field")); - } - continue; - } - - tok.text = tformat.substr(0, size); - m_tokens.push_back(tok); - - tformat.erase(0, size); - } -} - -void json_event_formatter::resolve_format(json_event *ev, std::list> &resolved) -{ - vector values; - for(auto tok : m_tokens) - { - if(tok.check) - { - values.clear(); - tok.check->extract(ev, values); - - const json_event_filter_check::values_t &evals = - tok.check->extracted_values(); - - std::string res_str = json_event_filter_check::no_value; - if(evals.size() == 1) - { - res_str = evals.at(0).as_string(); - } - else if (evals.size() > 1) - { - res_str = "("; - for(auto &val : evals) - { - if(res_str != "(") - { - res_str += ","; - } - res_str += val.as_string(); - } - res_str += ")"; - } - - resolved.push_back(std::make_pair(tok.check->field(), res_str)); - } - else - { - resolved.push_back(std::make_pair("", tok.text)); - } - } -} - -gen_event_formatter::output_format json_event_formatter::get_output_format() -{ - return m_output_format; -} - -json_event_formatter_factory::json_event_formatter_factory(std::shared_ptr json_factory) - : m_output_format(gen_event_formatter::OF_NORMAL), m_json_factory(json_factory) -{ -} - -json_event_formatter_factory::~json_event_formatter_factory() -{ -} - -void json_event_formatter_factory::set_output_format(gen_event_formatter::output_format of) -{ - m_formatters.clear(); - - m_output_format = of; -} - -std::shared_ptr json_event_formatter_factory::create_formatter(const std::string &format) -{ - auto it = m_formatters.find(format); - - if (it != m_formatters.end()) - { - return it->second; - } - - std::shared_ptr ret; - - ret.reset(new json_event_formatter(m_json_factory)); - - ret->set_format(m_output_format, format); - m_formatters[format] = ret; - - return ret; -} diff --git a/userspace/engine/json_evt.h b/userspace/engine/json_evt.h deleted file mode 100644 index 254d7e59f72..00000000000 --- a/userspace/engine/json_evt.h +++ /dev/null @@ -1,512 +0,0 @@ -// 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. -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "falco_common.h" -#include "prefix_search.h" -#include - -class json_event : public gen_event -{ -public: - json_event(); - virtual ~json_event(); - - void set_jevt(nlohmann::json &evt, uint64_t ts); - const nlohmann::json &jevt(); - - uint64_t get_ts() const; - - inline uint16_t get_source() const - { - return ESRC_K8S_AUDIT; - } - - inline uint16_t get_type() const - { - // All k8s audit events have the single tag "1". - see falco_engine::process_k8s_audit_event - return ppm_event_code::PPME_PLUGINEVENT_E; - } - -protected: - nlohmann::json m_jevt; - - uint64_t m_event_ts; -}; - -namespace falco_k8s_audit { - - // - // Given a raw json object, return a list of k8s audit event - // objects that represent the object. This method handles - // things such as EventList splitting. - // - // Returns true if the json object was recognized as a k8s - // audit event(s), false otherwise. - // - bool parse_k8s_audit_json(nlohmann::json &j, std::list &evts, bool top=true); -}; - -// A class representing an extracted value or a value on the rhs of a -// filter_check. This intentionally doesn't use the same types as -// ppm_events_public.h to take advantage of actual classes instead of -// lower-level pointers pointing to syscall events and to allow for -// efficient set comparisons. - -class json_event_value -{ -public: - enum param_type { - JT_STRING, - JT_INT64, - JT_INT64_PAIR - }; - - json_event_value(); - json_event_value(const std::string &val); - json_event_value(int64_t val); - virtual ~json_event_value(); - - param_type ptype() const; - - std::string as_string() const; - - bool operator==(const json_event_value &val) const; - bool operator!=(const json_event_value &val) const; - bool operator<(const json_event_value &val) const; - bool operator>(const json_event_value &val) const; - - // Only meaningful for string types - bool startswith(const json_event_value &val) const; - bool contains(const json_event_value &val) const; - -private: - param_type m_type; - - static bool parse_as_pair_int64(std::pair &pairval, const std::string &val); - static bool parse_as_int64(int64_t &intval, const std::string &val); - - // The number of possible types is small so far, so sticking - // with separate vars - - std::string m_stringval; - int64_t m_intval; - std::pair m_pairval; -}; - -class json_event_filter_check : public gen_event_filter_check -{ -public: - - static std::string no_value; - - enum index_mode - { - IDX_REQUIRED, - IDX_ALLOWED, - IDX_NONE - }; - - static std::vector s_index_mode_strs; - - enum index_type - { - IDX_KEY, - IDX_NUMERIC - }; - - static std::vector s_index_type_strs; - - // A struct describing a single filtercheck field ("ka.user") - struct field_info - { - std::string m_name; - std::string m_desc; - - index_mode m_idx_mode; - index_type m_idx_type; - - bool m_uses_paths; - - // The variants allow for brace-initialization either - // with just the name/desc or additionally with index - // information - field_info(); - field_info(std::string name, std::string desc); - field_info(std::string name, std::string desc, index_mode mode); - field_info(std::string name, std::string desc, index_mode mode, index_type itype); - field_info(std::string name, std::string desc, index_mode mode, index_type itype, bool uses_paths); - virtual ~field_info(); - }; - - // A struct describing a group of filtercheck fields ("ka") - struct check_info - { - std::string m_name; - std::string m_shortdesc; - std::string m_desc; - - std::list m_fields; - }; - - json_event_filter_check(); - virtual ~json_event_filter_check() = 0; - - virtual int32_t parse_field_name(const char *str, bool alloc_state, bool needed_for_filtering); - void add_filter_value(const char *str, uint32_t len, uint32_t i = 0); - bool compare(gen_event *evt); - - // This is adapted to support the new extract() method signature that - // supports extracting list of values, however json_evt was implemented - // to support this feature in the first place through the - // extracted_values_t structure. As such, for now this is only used for - // signature compliance, and always pushes a single value. The value pushed - // in the vector is a a const extracted_values_t* that points to the - // internal m_evalues. This is a temporary workaround to sync with the - // latest falcosecurity/libs development without re-designing the whole K8S - // support, which will eventually be refactored as a plugin in the future anyway. - bool extract(gen_event *evt, std::vector& values, bool sanitize_strings = true) final; - - const std::string &field(); - const std::string &idx(); - - // The combined size of the field, index, and surrounding - // brackets (e.g. ka.image[foo]) - size_t parsed_size(); - - virtual const check_info &get_info() const = 0; - - // - // Allocate a new check of the same type. Must be overridden. - // - virtual json_event_filter_check *allocate_new() = 0; - - // Subclasses or extraction functions can call this method to add each extracted value. - void add_extracted_value(const std::string &val); - void add_extracted_value_num(int64_t val); - - // After calling extract, you can call extracted_values to get - // the values extracted from an event. - typedef std::vector values_t; - const values_t &extracted_values(); - -protected: - // Subclasses can override this method, calling - // add_extracted_value to add extracted values. - virtual bool extract_values(json_event *jevt); - - static std::string json_as_string(const nlohmann::json &j); - - // Subclasses can define field names that act as aliases for - // specific json pointer expressions e.g. ka.user == - // jevt.value[/user/username]. This struct represents one of - // those aliases. - - // An alias might define an alternative function to extract - // values instead of using a json pointer. An example is - // ka.uri.param, which parses the query string to extract - // key=value parameters. - typedef std::function extract_t; - - struct alias - { - // The variants allow for brace-initialization either - // with just the pointer list or with a custom - // extraction function. - alias(); - alias(std::list ptrs); - alias(extract_t extract); - - virtual ~alias(); - - // A json pointer used to extract a referenced value - // from a json object. The pointers are applied in - // order. After applying a pointer, if the resulting - // object is an array, each array member is considered - // for subsequent json pointers. - // - // This allows for "plucking" items out of an array - // selected by an earlier json pointer. - std::list m_jptrs; - - extract_t m_extract; - }; - - // This map defines the aliases defined by this filter check - // class. - // - // The version of parse_field_name in this base class will - // check a field specification against all the aliases. - virtual const std::unordered_map &get_aliases() const = 0; - - //check_info m_info; - - // The actual field name parsed in parse_field_name. - std::string m_field; - - // The field name itself might include an index component - // e.g. ka.value[idx]. This holds the index. - std::string m_idx; - -private: - typedef std::set values_set_t; - typedef std::pair extracted_values_t; - - // The default extraction function uses the list of pointers - // in m_jptrs. Iterates over array elements between pointers if - // found. - bool def_extract(const nlohmann::json &j, - const std::list &ptrs, - std::list::iterator it); - - // The actual json pointer value to use to extract from - // events. See alias struct for usage. - std::list m_jptrs; - - // The extraction function to use. May not be defined, in which - // case the default function is used. - extract_t m_extract; - - // All values specified on the right hand side of the operator - // e.g. "ka.ns in ("one","two","three"), m_values has ("one", - // "two", "three") - values_set_t m_values; - - // All values extracted from the object by the field e.g. for - // a field ka.req.container.image returns all container images - // for all pods within a request. - extracted_values_t m_evalues; - - // If true, this filtercheck works on paths, which enables - // some extra bookkeeping to allow for path prefix searches. - bool m_uses_paths = false; - - path_prefix_search m_prefix_search; -}; - -class jevt_filter_check : public json_event_filter_check -{ -public: - jevt_filter_check(); - virtual ~jevt_filter_check(); - - int32_t parse_field_name(const char* str, bool alloc_state, bool needed_for_filtering) final; - - json_event_filter_check *allocate_new() override; - const check_info &get_info() const override; - -protected: - - bool extract_values(json_event *jevt) final; - const std::unordered_map &get_aliases() const override - { - static std::unordered_map a; - return a; - }; - - -private: - - // When the field is jevt_value, a json pointer representing - // the index in m_idx - nlohmann::json::json_pointer m_idx_ptr; - - static std::string s_jevt_time_field; - static std::string s_jevt_time_iso_8601_field; - static std::string s_jevt_rawtime_field; - static std::string s_jevt_obj_field; - static std::string s_jevt_value_field; -}; - -class k8s_audit_filter_check : public json_event_filter_check -{ -public: - k8s_audit_filter_check(); - virtual ~k8s_audit_filter_check(); - - json_event_filter_check *allocate_new() override; - - const check_info &get_info() const override; - const std::unordered_map &get_aliases() const override; - - // Extract all images/image repositories from the provided containers - static bool extract_images(const nlohmann::json &j, - json_event_filter_check &jchk); - - // Extract all query parameters - static bool extract_query_param(const nlohmann::json &j, - json_event_filter_check &jchk); - - // Extract some property from the set of rules in the request object - static bool extract_rule_attrs(const nlohmann::json &j, - json_event_filter_check &jchk); - - // Determine if the provided path matches any volumes host path. - static bool check_volumes_hostpath(const nlohmann::json &j, - json_event_filter_check &jchk); - - // Extract the volume types from volumes in the request object - static bool extract_volume_types(const nlohmann::json &j, - json_event_filter_check &jchk); - - // Extract all hostPort values from containers in the request object - static bool extract_host_port(const nlohmann::json &j, - json_event_filter_check &jchk); - - // Using both the pod and container security contexts, extract - // the uid/gid that will be used for each container. - static bool extract_effective_run_as(const nlohmann::json &j, - json_event_filter_check &jchk); - - // These are only used for compatibility with older rules files - - // Always return the string "N/A" - static bool always_return_na(const nlohmann::json &j, - json_event_filter_check &jchk); - - - // Return true if any container has privileged=true - static bool extract_any_privileged(const nlohmann::json &j, - json_event_filter_check &jchk); -}; - - -class json_event_filter : public sinsp_filter -{ -public: - json_event_filter(); - virtual ~json_event_filter(); - - std::string m_rule; - uint32_t m_rule_idx; - std::set m_tags; -}; - -class json_event_filter_factory : public gen_event_filter_factory -{ -public: - json_event_filter_factory(); - virtual ~json_event_filter_factory(); - - // Create a new filter - gen_event_filter *new_filter(); - - // Create a new filter_check - gen_event_filter_check *new_filtercheck(const char *fldname); - - // All defined field names - std::list get_fields(); - -private: - std::list> m_defined_checks; - std::list m_info; -}; - -class json_event_formatter : public gen_event_formatter -{ -public: - json_event_formatter(std::shared_ptr factory); - virtual ~json_event_formatter(); - - void set_format(output_format of, const std::string &format) override; - bool tostring(gen_event *evt, std::string &output) override; - bool tostring_withformat(gen_event *evt, std::string &output, gen_event_formatter::output_format of) override; - bool get_field_values(gen_event *evt, std::map &fields) override; - void get_field_names(std::vector &fields) override - { - throw falco_exception("json_event_formatter::get_field_names operation not supported"); - } - output_format get_output_format() override; - - std::string tojson(json_event *ev); - - // Split the format string into a list of tuples, broken at - // output fields, where each tuple is either a block of text - // from the original format string, or a field value/pair from - // the original format string. - // - // For example, given a format string "some output - // (%proc.name)", this will fill in resolved with 3 tuples: - // - ["", "some output ("] - // - ["proc.name", "nginx"] - // - ["", ")"] - // - // This can be used either to return a resolved output string - // or a map of field name/value pairs. - void resolve_format(json_event *ev, std::list> &resolved); - -private: - void parse_format(); - - // A format token is either a combination of a filtercheck - // name (ka.value) and filtercheck object as key, or an empty - // key and a NULL filtercheck object, combined with a value ( - // - // For example, given a format string: - // "The value is %ka.value today" - // The tokens would be: - // [("The value is ", NULL), ("ka.value", ), " today", NULL)] - - struct fmt_token - { - std::string text; - std::shared_ptr check; - }; - - gen_event_formatter::output_format m_output_format; - - // The original format string - std::string m_format; - - // The chunks that make up the format string, in order, broken - // up between text chunks and filterchecks. - std::list m_tokens; - - // All the filterchecks required to resolve tokens in the format string - std::shared_ptr m_json_factory; -}; - -class json_event_formatter_factory : public gen_event_formatter_factory -{ -public: - json_event_formatter_factory(std::shared_ptr factory); - virtual ~json_event_formatter_factory(); - - void set_output_format(gen_event_formatter::output_format of) override; - - std::shared_ptr create_formatter(const std::string &format) override; - -protected: - // Maps from output string to formatter - std::map> m_formatters; - - gen_event_formatter::output_format m_output_format; - - // All the filterchecks required to resolve tokens in the format string - std::shared_ptr m_json_factory; -};