Files
falco/userspace/engine/json_evt.cpp
2022-01-14 13:29:33 +01:00

1740 lines
46 KiB
C++

/*
Copyright (C) 2019 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 <ctype.h>
#include "uri.h"
#include "utils.h"
#include "falco_common.h"
#include "json_evt.h"
#include "banned.h" // This raises a compilation error when certain functions are used
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<json_event> &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", "<NA>") == "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, "<NA>"), 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", "<NA>") == "Event")
{
evts.emplace_back();
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(j.value(k8s_audit_time, "<NA>"), 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<int64_t,int64_t> &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 = "<NA>";
std::vector<std::string> json_event_filter_check::s_index_mode_strs = {"IDX_REQUIRED", "IDX_ALLOWED", "IDX_NONE"};
std::vector<std::string> 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<nlohmann::json::json_pointer> &ptrs,
std::list<nlohmann::json::json_pointer>::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<nlohmann::json::json_pointer> 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(auto &info : m_info.m_fields)
{
if(m_aliases.find(info.m_name) == m_aliases.end())
{
throw falco_exception("Could not find alias for field name " + info.m_name);
}
m_uses_paths = info.m_uses_paths;
auto &al = m_aliases[info.m_name];
// 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;
uint32_t len;
auto evalues = (const extracted_values_t *) extract(jevt, &len);
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;
}
}
json_event_filter_check::check_info &json_event_filter_check::get_info()
{
return m_info;
}
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));
}
uint8_t *json_event_filter_check::extract(gen_event *evt, uint32_t *len, 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);
}
*len = sizeof(m_evalues);
return (uint8_t *)&m_evalues;
}
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";
jevt_filter_check::jevt_filter_check()
{
m_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 [<json pointer expression>]. The property is returned as a string", IDX_REQUIRED, IDX_KEY},
{s_jevt_obj_field, "The entire json object, stringified"}}};
}
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 [<json pointer expression>]
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[<json pointer>]'");
}
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<std::string> 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<std::string> param_parts = sinsp_split(part, '=');
if(param_parts.size() == 2 && uri::decode(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;
}
k8s_audit_filter_check::k8s_audit_filter_check()
{
m_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"}}};
{
m_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}}}};
}
}
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()
{
}
json_event_filter::~json_event_filter()
{
}
json_event_filter_factory::json_event_filter_factory()
{
m_defined_checks.push_back(shared_ptr<json_event_filter_check>(new jevt_filter_check()));
m_defined_checks.push_back(shared_ptr<json_event_filter_check>(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<gen_event_filter_factory::filter_fieldclass_info> json_event_filter_factory::get_fields()
{
std::list<gen_event_filter_factory::filter_fieldclass_info> 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)
{
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(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<gen_event_filter_factory> 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<json_event *>(gevt);
if(of == OF_JSON)
{
output = tojson(ev);
return true;
}
else
{
std::list<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>> 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 "<NA>" 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<std::string, std::string> &fields)
{
json_event *ev = static_cast<json_event *>(gevt);
std::list<std::pair<std::string, std::string>> 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 = "<NA>";
}
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<std::pair<std::string, std::string>> &resolved)
{
for(auto tok : m_tokens)
{
if(tok.check)
{
uint32_t len;
(void) tok.check->extract(ev, &len);
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<gen_event_filter_factory> 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<gen_event_formatter> 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<gen_event_formatter> ret;
ret.reset(new json_event_formatter(m_json_factory));
ret->set_format(m_output_format, format);
m_formatters[format] = ret;
return ret;
}