diff --git a/CMakeLists.txt b/CMakeLists.txt index fecd0e632c..4c24609b5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ endif() project(falcosecurity-libs) option(USE_BUNDLED_DEPS "Enable bundled dependencies instead of using the system ones" ON) -option(MINIMAL_BUILD "Produce a minimal build with only the essential features (no kubernetes, no mesos, no marathon and no container metadata)" OFF) +option(MINIMAL_BUILD "Produce a minimal build with only the essential features (no container metadata)" OFF) option(MUSL_OPTIMIZED_BUILD "Enable if you want a musl optimized build" OFF) option(USE_BUNDLED_DRIVER "Use the driver/ subdirectory in the build process (only available in Linux)" ON) option(ENABLE_DRIVERS_TESTS "Enable driver tests (bpf, kernel module, modern bpf)" OFF) diff --git a/userspace/libsinsp/CMakeLists.txt b/userspace/libsinsp/CMakeLists.txt index 1bee6ec812..6cfe839a09 100644 --- a/userspace/libsinsp/CMakeLists.txt +++ b/userspace/libsinsp/CMakeLists.txt @@ -99,8 +99,6 @@ set(SINSP_SOURCES sinsp_filtercheck_fspath.cpp sinsp_filtercheck_gen_event.cpp sinsp_filtercheck_group.cpp - sinsp_filtercheck_k8s.cpp - sinsp_filtercheck_mesos.cpp sinsp_filtercheck_rawstring.cpp sinsp_filtercheck_reference.cpp sinsp_filtercheck_syslog.cpp diff --git a/userspace/libsinsp/filter_check_list.cpp b/userspace/libsinsp/filter_check_list.cpp index f4f9c2bac6..64cc64da09 100644 --- a/userspace/libsinsp/filter_check_list.cpp +++ b/userspace/libsinsp/filter_check_list.cpp @@ -121,8 +121,6 @@ sinsp_filter_check_list::sinsp_filter_check_list() add_filter_check(new sinsp_filter_check_syslog()); add_filter_check(new sinsp_filter_check_utils()); add_filter_check(new sinsp_filter_check_fdlist()); - add_filter_check(new sinsp_filter_check_k8s()); - add_filter_check(new sinsp_filter_check_mesos()); add_filter_check(new sinsp_filter_check_tracer()); add_filter_check(new sinsp_filter_check_evtin()); } diff --git a/userspace/libsinsp/filterchecks.h b/userspace/libsinsp/filterchecks.h index 9870c4cd73..d673eda709 100644 --- a/userspace/libsinsp/filterchecks.h +++ b/userspace/libsinsp/filterchecks.h @@ -30,8 +30,6 @@ limitations under the License. #include "sinsp_filtercheck_fspath.h" #include "sinsp_filtercheck_gen_event.h" #include "sinsp_filtercheck_group.h" -#include "sinsp_filtercheck_k8s.h" -#include "sinsp_filtercheck_mesos.h" #include "sinsp_filtercheck_rawstring.h" #include "sinsp_filtercheck_reference.h" #include "sinsp_filtercheck_syslog.h" diff --git a/userspace/libsinsp/sinsp.h b/userspace/libsinsp/sinsp.h index 82d69ec0e6..7d97311a5f 100644 --- a/userspace/libsinsp/sinsp.h +++ b/userspace/libsinsp/sinsp.h @@ -1205,7 +1205,7 @@ VISIBILITY_PRIVATE std::vector m_decoders_reset_list; // - // meta event management for other sources like k8s, mesos. + // meta event management for other sources. // sinsp_evt* m_metaevt; meta_event_callback m_meta_event_callback; @@ -1360,8 +1360,6 @@ VISIBILITY_PRIVATE friend class sinsp_worker; friend class curses_textbox; friend class sinsp_filter_check_fd; - friend class sinsp_filter_check_k8s; - friend class sinsp_filter_check_mesos; friend class sinsp_filter_check_evtin; friend class sinsp_baseliner; friend class sinsp_memory_dumper; diff --git a/userspace/libsinsp/sinsp_filtercheck_container.cpp b/userspace/libsinsp/sinsp_filtercheck_container.cpp index 556cb16358..36747a1b4c 100644 --- a/userspace/libsinsp/sinsp_filtercheck_container.cpp +++ b/userspace/libsinsp/sinsp_filtercheck_container.cpp @@ -32,6 +32,27 @@ using namespace std; return (uint8_t*) (x).c_str(); \ } while(0) +// We use a macro to avoid a performance overhead, even using an inline function +// we are not sure the compiler will inline it +#define CHECK_LABEL_PRESENCE(x) \ + if(is_host || !container_info) \ + { \ + return nullptr; \ + } \ + else \ + { \ + auto label = container_info->m_labels.find(x); \ + if(label != container_info->m_labels.end()) \ + { \ + m_tstr = label->second; \ + RETURN_EXTRACT_STRING(m_tstr); \ + } \ + else \ + { \ + return nullptr; \ + } \ + } + static const filtercheck_field_info sinsp_filter_check_container_fields[] = { {PT_CHARBUF, EPF_NONE, PF_NA, "container.id", "Container ID", "The truncated container id (first 12 characters)."}, @@ -57,6 +78,11 @@ static const filtercheck_field_info sinsp_filter_check_container_fields[] = {PT_RELTIME, EPF_NONE, PF_DEC, "container.duration", "Number of nanoseconds since container.start_ts", "Number of nanoseconds since container.start_ts."}, {PT_CHARBUF, EPF_NONE, PF_NA, "container.ip", "Container ip address", "The container's / pod's primary ip address as retrieved from the container engine. Only ipv4 addresses are tracked. Consider container.cni.json (CRI use case) for logging ip addresses for each network interface."}, {PT_CHARBUF, EPF_NONE, PF_NA, "container.cni.json", "Container's / pod's CNI result json", "The container's / pod's CNI result field from the respective pod status info. It contains ip addresses for each network interface exposed as unparsed escaped JSON string. Supported for CRI container engine (containerd, cri-o runtimes), optimized for containerd (some non-critical JSON keys removed). Useful for tracking ips (ipv4 and ipv6, dual-stack support) for each network interface (multi-interface support)."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.k8s.pod.name", "Pod Name", "When the container belongs to a Kubernetes pod it provides the pod name."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.k8s.pod.id", "Pod ID", "When the container belongs to a Kubernetes pod it provides the pod id."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.k8s.pod.label", "Pod Label", "When the container belongs to a Kubernetes pod it provides a specific pod label. 'container.k8s.pod.label[foo]'."}, + {PT_CHARBUF, EPF_IS_LIST, PF_NA, "container.k8s.pod.labels", "Pod Labels", "When the container belongs to a Kubernetes pod it provides the pod comma-separated key/value labels. E.g. '(foo1:bar1,foo2:bar2)'."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.k8s.ns.name", "Pod Namespace Name", "When the container belongs to a Kubernetes pod it provides the pod namespace name."}, }; sinsp_filter_check_container::sinsp_filter_check_container() @@ -73,34 +99,47 @@ sinsp_filter_check* sinsp_filter_check_container::allocate_new() return (sinsp_filter_check*) new sinsp_filter_check_container(); } -int32_t sinsp_filter_check_container::extract_arg(const string &val, size_t basepos) +int32_t sinsp_filter_check_container::extract_arg(const std::string& val, size_t basepos, container_arg_type type) { size_t start = val.find_first_of('[', basepos); if(start == string::npos) { - throw sinsp_exception("filter syntax error: " + val); + throw sinsp_exception("the field '" + val + "' requires an argument but '[' is not found"); } size_t end = val.find_first_of(']', start); if(end == string::npos) { - throw sinsp_exception("filter syntax error: " + val); + throw sinsp_exception("the field '" + val + "' requires an argument but ']' is not found"); } - string numstr = val.substr(start + 1, end-start-1); - try - { - m_argid = sinsp_numparser::parsed32(numstr); - } - catch (const sinsp_exception& e) + string str = val.substr(start + 1, end-start-1); + + if(type == container_arg_type::TYPE_S32) { - if(strstr(e.what(), "is not a valid number") == NULL) + try { - throw; + m_argid = sinsp_numparser::parsed32(str); } + catch (const sinsp_exception& e) + { + if(strstr(e.what(), "is not a valid number") == NULL) + { + throw; + } - m_argid = -1; - m_argstr = numstr; + m_argid = -1; + m_argstr = str; + } + } + else if(type == container_arg_type::TYPE_STRING) + { + m_argstr = str; + } + else + { + ASSERT(false); + throw sinsp_exception("argument type unknown during 'sinsp_filter_check_container::extract_arg'"); } return end+1; @@ -149,7 +188,7 @@ int32_t sinsp_filter_check_container::parse_field_name(const char* str, bool all } m_field = &m_info.m_fields[m_field_id]; - res = extract_arg(val, basepos); + res = extract_arg(val, basepos, container_arg_type::TYPE_S32); } else if (val.find("container.mount") == 0 && val[basepos-1] != 's') @@ -157,7 +196,17 @@ int32_t sinsp_filter_check_container::parse_field_name(const char* str, bool all m_field_id = TYPE_CONTAINER_MOUNT; m_field = &m_info.m_fields[m_field_id]; - res = extract_arg(val, basepos-1); + res = extract_arg(val, basepos-1, container_arg_type::TYPE_S32); + } + // We need to check that is `pod.label` and not `pod.labels` + else if(val.find("container.k8s.pod.label") == 0 && + val[sizeof("container.k8s.pod.label")-1] != 's') + { + m_field_id = TYPE_CONTAINER_K8S_POD_LABEL; + m_field = &m_info.m_fields[m_field_id]; + + // We don't need to check the return value, if no arg was specified we throw an exception + res = extract_arg(val, sizeof("container.k8s.pod.label")-1, container_arg_type::TYPE_STRING); } else { @@ -574,6 +623,48 @@ uint8_t* sinsp_filter_check_container::extract(sinsp_evt *evt, OUT uint32_t* len RETURN_EXTRACT_STRING(container_info->m_pod_cniresult); } break; + case TYPE_CONTAINER_K8S_POD_NAME: + CHECK_LABEL_PRESENCE("io.kubernetes.pod.name"); + break; + case TYPE_CONTAINER_K8S_POD_ID: + CHECK_LABEL_PRESENCE("io.kubernetes.pod.uid"); + break; + case TYPE_CONTAINER_K8S_POD_LABEL: + if(is_host || !container_info) + { + return nullptr; + } + else + { + auto label = container_info->m_labels.find("io.kubernetes.sandbox.id"); + if(label == container_info->m_labels.end()) + { + return nullptr; + } + + std::string sandbox_container_id = label->second; + if(sandbox_container_id.size() > 12) + { + sandbox_container_id.resize(12); + } + const auto sandbox_container_info = m_inspector->m_container_manager.get_container(sandbox_container_id); + if(!sandbox_container_info || sandbox_container_info->m_labels.empty()) + { + return nullptr; + } + + label = sandbox_container_info->m_labels.find(m_argstr); + if(label == sandbox_container_info->m_labels.end()) + { + return nullptr; + } + m_tstr = label->second; + RETURN_EXTRACT_STRING(m_tstr); + } + break; + case TYPE_CONTAINER_K8S_NS_NAME: + CHECK_LABEL_PRESENCE("io.kubernetes.pod.namespace"); + break; default: ASSERT(false); break; @@ -581,3 +672,72 @@ uint8_t* sinsp_filter_check_container::extract(sinsp_evt *evt, OUT uint32_t* len return NULL; } + +bool sinsp_filter_check_container::extract(sinsp_evt* evt, OUT std::vector& values, + bool sanitize_strings) +{ + values.clear(); + + // `TYPE_CONTAINER_K8S_POD_LABELS` has the `EPF_IS_LIST` flag. + if(m_field_id == TYPE_CONTAINER_K8S_POD_LABELS) + { + sinsp_threadinfo* tinfo = evt->get_thread_info(); + // Without the container id we can do nothing + if(tinfo == nullptr || tinfo->m_container_id.empty()) + { + return false; + } + + auto container_info = m_inspector->m_container_manager.get_container(tinfo->m_container_id); + + if(container_info == nullptr) + { + return false; + } + + // We need the label `io.kubernetes.sandbox.id` to extract pod labels. + auto sandbox_label = container_info->m_labels.find("io.kubernetes.sandbox.id"); + if(sandbox_label == container_info->m_labels.end()) + { + return false; + } + + std::string sandbox_container_id = sandbox_label->second; + if(sandbox_container_id.size() > 12) + { + sandbox_container_id.resize(12); + } + const auto sandbox_container_info = + m_inspector->m_container_manager.get_container(sandbox_container_id); + + if(sandbox_container_info == nullptr || sandbox_container_info->m_labels.empty()) + { + return false; + } + + // Clean a possible not empty storage + m_labels_storage.clear(); + for(const auto& label : sandbox_container_info->m_labels) + { + // Exclude annotations and internal labels + if(label.first.find("annotation.") == 0 || label.first.find("io.kubernetes.") == 0) + { + continue; + } + + // We use a storage because these strings should survive until the next filtercheck call. + m_labels_storage.emplace_back(label.first + ":" + label.second); + } + + for(const auto& label : m_labels_storage) + { + extract_value_t val; + val.ptr = (uint8_t*)label.c_str(); + val.len = label.size(); + values.emplace_back(val); + } + return true; + } + + return sinsp_filter_check::extract(evt, values, sanitize_strings); +} diff --git a/userspace/libsinsp/sinsp_filtercheck_container.h b/userspace/libsinsp/sinsp_filtercheck_container.h index 8edda0cb5a..746599d918 100644 --- a/userspace/libsinsp/sinsp_filtercheck_container.h +++ b/userspace/libsinsp/sinsp_filtercheck_container.h @@ -51,17 +51,32 @@ class sinsp_filter_check_container : public sinsp_filter_check TYPE_CONTAINER_DURATION, TYPE_CONTAINER_IP_ADDR, TYPE_CONTAINER_CNIRESULT, + TYPE_CONTAINER_K8S_POD_NAME, + TYPE_CONTAINER_K8S_POD_ID, + TYPE_CONTAINER_K8S_POD_LABEL, + TYPE_CONTAINER_K8S_POD_LABELS, + TYPE_CONTAINER_K8S_NS_NAME, + }; + + enum container_arg_type { + TYPE_S32, + TYPE_STRING }; sinsp_filter_check_container(); sinsp_filter_check* allocate_new(); uint8_t* extract(sinsp_evt *evt, OUT uint32_t* len, bool sanitize_strings = true); + // This is needed to extract arguments with `EPF_IS_LIST` flags + bool extract(sinsp_evt *evt, OUT std::vector& values, bool sanitize_strings = true); const std::string &get_argstr(); private: int32_t parse_field_name(const char* str, bool alloc_state, bool needed_for_filtering); - int32_t extract_arg(const std::string& val, size_t basename); + int32_t extract_arg(const std::string& val, size_t basepos, container_arg_type type); + // This vector is used to save the concatenated labels (`key:value`) + // between invocations of `TYPE_CONTAINER_K8S_POD_LABELS` filtercheck. + std::vector m_labels_storage; std::string m_tstr; uint32_t m_u32val; int32_t m_argid; diff --git a/userspace/libsinsp/sinsp_filtercheck_k8s.cpp b/userspace/libsinsp/sinsp_filtercheck_k8s.cpp deleted file mode 100644 index b8b4cbeb0c..0000000000 --- a/userspace/libsinsp/sinsp_filtercheck_k8s.cpp +++ /dev/null @@ -1,282 +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 "sinsp_filtercheck_k8s.h" -#include "sinsp.h" -#include "sinsp_int.h" - -using namespace std; - -#define RETURN_EXTRACT_STRING(x) do { \ - *len = (x).size(); \ - return (uint8_t*) (x).c_str(); \ -} while(0) - -static inline bool str_match_start(const std::string& val, size_t len, const char* m) -{ - return val.compare(0, len, m) == 0; -} - -#define STR_MATCH(s) str_match_start(val, sizeof (s) -1, s) - -static const filtercheck_field_info sinsp_filter_check_k8s_fields[] = -{ - {PT_CHARBUF, EPF_NONE, PF_NA, "k8s.pod.name", "Pod Name", "Kubernetes pod name."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "k8s.pod.id", "Pod ID", "Kubernetes pod id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "k8s.pod.label", "Pod Label", "Kubernetes pod label. E.g. 'k8s.pod.label.foo'."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "k8s.pod.labels", "Pod Labels", "Kubernetes pod comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "k8s.pod.ip", "Pod Ip", "Kubernetes pod ip, same as container.ip field as each container in a pod shares the network stack of the sandbox / pod. Only ipv4 addresses are tracked. Consider k8s.pod.cni.json for logging ip addresses for each network interface."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "k8s.pod.cni.json", "Pod CNI result json", "Kubernetes pod CNI result field from the respective pod status info, same as container.cni.json field. It contains ip addresses for each network interface exposed as unparsed escaped JSON string. Supported for CRI container engine (containerd, cri-o runtimes), optimized for containerd (some non-critical JSON keys removed). Useful for tracking ips (ipv4 and ipv6, dual-stack support) for each network interface (multi-interface support)."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.rc.name", "Replication Controller Name", "Kubernetes replication controller name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.rc.id", "Replication Controller ID", "Kubernetes replication controller id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "k8s.rc.label", "Replication Controller Label", "Kubernetes replication controller label. E.g. 'k8s.rc.label.foo'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.rc.labels", "Replication Controller Labels", "Kubernetes replication controller comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.svc.name", "Service Name", "Kubernetes service name (can return more than one value, concatenated)."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.svc.id", "Service ID", "Kubernetes service id (can return more than one value, concatenated)."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "k8s.svc.label", "Service Label", "Kubernetes service label. E.g. 'k8s.svc.label.foo' (can return more than one value, concatenated)."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.svc.labels", "Service Labels", "Kubernetes service comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "k8s.ns.name", "Namespace Name", "Kubernetes namespace name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.ns.id", "Namespace ID", "Kubernetes namespace id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "k8s.ns.label", "Namespace Label", "Kubernetes namespace label. E.g. 'k8s.ns.label.foo'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.ns.labels", "Namespace Labels", "Kubernetes namespace comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.rs.name", "Replica Set Name", "Kubernetes replica set name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.rs.id", "Replica Set ID", "Kubernetes replica set id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "k8s.rs.label", "Replica Set Label", "Kubernetes replica set label. E.g. 'k8s.rs.label.foo'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.rs.labels", "Replica Set Labels", "Kubernetes replica set comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.deployment.name", "Deployment Name", "Kubernetes deployment name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.deployment.id", "Deployment ID", "Kubernetes deployment id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "k8s.deployment.label", "Deployment Label", "Kubernetes deployment label. E.g. 'k8s.rs.label.foo'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "k8s.deployment.labels", "Deployment Labels", "Kubernetes deployment comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, -}; - -sinsp_filter_check_k8s::sinsp_filter_check_k8s() -{ - m_info.m_name = "k8s"; - m_info.m_desc = "Kubernetes related context. When configured to fetch from the API server, all fields are available. Otherwise, only the `k8s.pod.*` and `k8s.ns.name` fields are populated with data gathered from the container runtime."; - m_info.m_fields = sinsp_filter_check_k8s_fields; - m_info.m_nfields = sizeof(sinsp_filter_check_k8s_fields) / sizeof(sinsp_filter_check_k8s_fields[0]); - m_info.m_flags = filter_check_info::FL_WORKS_ON_THREAD_TABLE; -} - -sinsp_filter_check* sinsp_filter_check_k8s::allocate_new() -{ - return (sinsp_filter_check*) new sinsp_filter_check_k8s(); -} - -int32_t sinsp_filter_check_k8s::parse_field_name(const char* str, bool alloc_state, bool needed_for_filtering) -{ - string val(str); - - if(STR_MATCH("k8s.pod.label") && - !STR_MATCH("k8s.pod.labels")) - { - m_field_id = TYPE_K8S_POD_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("k8s.pod.label", val); - } - else if(STR_MATCH("k8s.rc.label") && - !STR_MATCH("k8s.rc.labels")) - { - m_field_id = TYPE_K8S_RC_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("k8s.rc.label", val); - } - else if(STR_MATCH("k8s.rs.label") && - !STR_MATCH("k8s.rs.labels")) - { - m_field_id = TYPE_K8S_RS_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("k8s.rs.label", val); - } - else if(STR_MATCH("k8s.svc.label") && - !STR_MATCH("k8s.svc.labels")) - { - m_field_id = TYPE_K8S_SVC_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("k8s.svc.label", val); - } - else if(STR_MATCH("k8s.ns.label") && - !STR_MATCH("k8s.ns.labels")) - { - m_field_id = TYPE_K8S_NS_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("k8s.ns.label", val); - } - else if(STR_MATCH("k8s.deployment.label") && - !STR_MATCH("k8s.deployment.labels")) - { - m_field_id = TYPE_K8S_DEPLOYMENT_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("k8s.deployment.label", val); - } - else - { - return sinsp_filter_check::parse_field_name(str, alloc_state, needed_for_filtering); - } -} - -int32_t sinsp_filter_check_k8s::extract_arg(const string& fldname, const string& val) -{ - int32_t parsed_len = 0; - - if(val[fldname.size()] == '.') - { - size_t endpos; - for(endpos = fldname.size() + 1; endpos < val.length(); ++endpos) - { - if(!isalnum(val[endpos]) - && val[endpos] != '/' - && val[endpos] != '_' - && val[endpos] != '-' - && val[endpos] != '.') - { - break; - } - } - - parsed_len = (uint32_t)endpos; - m_argname = val.substr(fldname.size() + 1, endpos - fldname.size() - 1); - } - else - { - throw sinsp_exception("filter syntax error: " + val); - } - - return parsed_len; -} - -void sinsp_filter_check_k8s::concatenate_container_labels(const map& labels, string* s) -{ - for (auto const& label_pair : labels) - { - // exclude annotations and internal labels - if (label_pair.first.find("annotation.") == 0 || label_pair.first.find("io.kubernetes.") == 0) { - continue; - } - if(!s->empty()) - { - s->append(", "); - } - s->append(label_pair.first); - if(!label_pair.second.empty()) - { - s->append(":" + label_pair.second); - } - } -} - -uint8_t* sinsp_filter_check_k8s::extract(sinsp_evt *evt, OUT uint32_t* len, bool sanitize_strings) -{ - *len = 0; - - ASSERT(evt); - if(evt == NULL) - { - ASSERT(false); - return NULL; - } - - sinsp_threadinfo* tinfo = evt->get_thread_info(); - if(tinfo == NULL) - { - return NULL; - } - m_tstr.clear(); - // there is metadata we can pull from the container directly instead of the k8s apiserver - const sinsp_container_info::ptr_t container_info = - m_inspector->m_container_manager.get_container(tinfo->m_container_id); - if(!tinfo->m_container_id.empty() && container_info && !container_info->m_labels.empty()) - { - switch(m_field_id) - { - case TYPE_K8S_POD_NAME: - if(container_info->m_labels.count("io.kubernetes.pod.name") > 0) - { - m_tstr = container_info->m_labels.at("io.kubernetes.pod.name"); - RETURN_EXTRACT_STRING(m_tstr); - } - break; - case TYPE_K8S_NS_NAME: - if(container_info->m_labels.count("io.kubernetes.pod.namespace") > 0) - { - m_tstr = container_info->m_labels.at("io.kubernetes.pod.namespace"); - RETURN_EXTRACT_STRING(m_tstr); - } - break; - case TYPE_K8S_POD_ID: - if(container_info->m_labels.count("io.kubernetes.pod.uid") > 0) - { - m_tstr = container_info->m_labels.at("io.kubernetes.pod.uid"); - RETURN_EXTRACT_STRING(m_tstr); - } - break; - case TYPE_K8S_POD_LABEL: - case TYPE_K8S_POD_LABELS: - if(container_info->m_labels.count("io.kubernetes.sandbox.id") > 0) - { - std::string sandbox_container_id; - sandbox_container_id = container_info->m_labels.at("io.kubernetes.sandbox.id"); - if(sandbox_container_id.size() > 12) - { - sandbox_container_id.resize(12); - } - const sinsp_container_info::ptr_t sandbox_container_info = - m_inspector->m_container_manager.get_container(sandbox_container_id); - if(sandbox_container_info && !sandbox_container_info->m_labels.empty()) - { - if (m_field_id == TYPE_K8S_POD_LABEL && sandbox_container_info->m_labels.count(m_argname) > 0) - { - m_tstr = sandbox_container_info->m_labels.at(m_argname); - RETURN_EXTRACT_STRING(m_tstr); - } - if (m_field_id == TYPE_K8S_POD_LABELS) - { - concatenate_container_labels(sandbox_container_info->m_labels, &m_tstr); - RETURN_EXTRACT_STRING(m_tstr); - } - } - - } - break; - case TYPE_K8S_POD_IP: - m_u32val = htonl(container_info->m_container_ip); - char addrbuff[100]; - inet_ntop(AF_INET, &m_u32val, addrbuff, sizeof(addrbuff)); - m_tstr = addrbuff; - RETURN_EXTRACT_STRING(m_tstr); - break; - case TYPE_K8S_POD_CNIRESULT: - RETURN_EXTRACT_STRING(container_info->m_pod_cniresult); - break; - default: - ASSERT(false); - break; - } - } - - // all the rest of the fields are deprecated and return NULL since - // we removed the k8s client from the inspector. - return NULL; -} - diff --git a/userspace/libsinsp/sinsp_filtercheck_k8s.h b/userspace/libsinsp/sinsp_filtercheck_k8s.h deleted file mode 100644 index 7be146351e..0000000000 --- a/userspace/libsinsp/sinsp_filtercheck_k8s.h +++ /dev/null @@ -1,67 +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 "sinsp_filtercheck.h" - -class sinsp_filter_check_k8s : public sinsp_filter_check -{ -public: - enum check_type - { - TYPE_K8S_POD_NAME = 0, - TYPE_K8S_POD_ID, - TYPE_K8S_POD_LABEL, - TYPE_K8S_POD_LABELS, - TYPE_K8S_POD_IP, - TYPE_K8S_POD_CNIRESULT, - TYPE_K8S_RC_NAME, - TYPE_K8S_RC_ID, - TYPE_K8S_RC_LABEL, - TYPE_K8S_RC_LABELS, - TYPE_K8S_SVC_NAME, - TYPE_K8S_SVC_ID, - TYPE_K8S_SVC_LABEL, - TYPE_K8S_SVC_LABELS, - TYPE_K8S_NS_NAME, - TYPE_K8S_NS_ID, - TYPE_K8S_NS_LABEL, - TYPE_K8S_NS_LABELS, - TYPE_K8S_RS_NAME, - TYPE_K8S_RS_ID, - TYPE_K8S_RS_LABEL, - TYPE_K8S_RS_LABELS, - TYPE_K8S_DEPLOYMENT_NAME, - TYPE_K8S_DEPLOYMENT_ID, - TYPE_K8S_DEPLOYMENT_LABEL, - TYPE_K8S_DEPLOYMENT_LABELS, - }; - - sinsp_filter_check_k8s(); - sinsp_filter_check* allocate_new(); - int32_t parse_field_name(const char* str, bool alloc_state, bool needed_for_filtering); - uint8_t* extract(sinsp_evt *evt, OUT uint32_t* len, bool sanitize_strings = true); - -private: - int32_t extract_arg(const std::string& fldname, const std::string& val); - void concatenate_container_labels(const std::map& labels, std::string* s); - std::string m_argname; - std::string m_tstr; - uint32_t m_u32val; -}; diff --git a/userspace/libsinsp/sinsp_filtercheck_mesos.cpp b/userspace/libsinsp/sinsp_filtercheck_mesos.cpp deleted file mode 100644 index b0d67ed1fa..0000000000 --- a/userspace/libsinsp/sinsp_filtercheck_mesos.cpp +++ /dev/null @@ -1,124 +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 "sinsp_filtercheck_mesos.h" -#include "sinsp.h" -#include "sinsp_int.h" - -using namespace std; - -static inline bool str_match_start(const std::string& val, size_t len, const char* m) -{ - return val.compare(0, len, m) == 0; -} - -#define STR_MATCH(s) str_match_start(val, sizeof (s) -1, s) - -static const filtercheck_field_info sinsp_filter_check_mesos_fields[] = -{ - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "mesos.task.name", "Task Name", "Mesos task name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "mesos.task.id", "Task ID", "Mesos task id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "mesos.task.label", "Task Label", "Mesos task label. E.g. 'mesos.task.label.foo'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "mesos.task.labels", "Task Labels", "Mesos task comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "mesos.framework.name", "Framework Name", "Mesos framework name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "mesos.framework.id", "Framework ID", "Mesos framework id."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "marathon.app.name", "App Name", "Marathon app name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "marathon.app.id", "App ID", "Marathon app id."}, - {PT_CHARBUF, EPF_ARG_REQUIRED|EPF_DEPRECATED, PF_NA, "marathon.app.label", "App Label", "Marathon app label. E.g. 'marathon.app.label.foo'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "marathon.app.labels", "App Labels", "Marathon app comma-separated key/value labels. E.g. 'foo1:bar1,foo2:bar2'."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "marathon.group.name", "Group Name", "Marathon group name."}, - {PT_CHARBUF, EPF_NONE|EPF_DEPRECATED, PF_NA, "marathon.group.id", "Group ID", "Marathon group id."}, -}; - -sinsp_filter_check_mesos::sinsp_filter_check_mesos() -{ - m_info.m_name = "mesos"; - m_info.m_desc = "Mesos related context."; - m_info.m_fields = sinsp_filter_check_mesos_fields; - m_info.m_nfields = sizeof(sinsp_filter_check_mesos_fields) / sizeof(sinsp_filter_check_mesos_fields[0]); - m_info.m_flags = filter_check_info::FL_WORKS_ON_THREAD_TABLE; -} - -sinsp_filter_check* sinsp_filter_check_mesos::allocate_new() -{ - return (sinsp_filter_check*) new sinsp_filter_check_mesos(); -} - -int32_t sinsp_filter_check_mesos::parse_field_name(const char* str, bool alloc_state, bool needed_for_filtering) -{ - string val(str); - - if(STR_MATCH("mesos.task.label") && - !STR_MATCH("mesos.task.labels")) - { - m_field_id = TYPE_MESOS_TASK_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("mesos.task.label", val); - } - else if(STR_MATCH("marathon.app.label") && - !STR_MATCH("marathon.app.labels")) - { - m_field_id = TYPE_MARATHON_APP_LABEL; - m_field = &m_info.m_fields[m_field_id]; - - return extract_arg("marathon.app.label", val); - } - else - { - return sinsp_filter_check::parse_field_name(str, alloc_state, needed_for_filtering); - } -} - -int32_t sinsp_filter_check_mesos::extract_arg(const string& fldname, const string& val) -{ - int32_t parsed_len = 0; - - if(val[fldname.size()] == '.') - { - size_t endpos; - for(endpos = fldname.size() + 1; endpos < val.length(); ++endpos) - { - if(!isalnum(val[endpos]) - && val[endpos] != '/' - && val[endpos] != '_' - && val[endpos] != '-' - && val[endpos] != '.') - { - break; - } - } - - parsed_len = (uint32_t)endpos; - m_argname = val.substr(fldname.size() + 1, endpos - fldname.size() - 1); - } - else - { - throw sinsp_exception("filter syntax error: " + val); - } - - return parsed_len; -} - -uint8_t* sinsp_filter_check_mesos::extract(sinsp_evt *evt, OUT uint32_t* len, bool sanitize_strings) -{ - // note: all mesos fields are deprecated since removing them from the codebase - *len = 0; - return NULL; -} - diff --git a/userspace/libsinsp/sinsp_filtercheck_mesos.h b/userspace/libsinsp/sinsp_filtercheck_mesos.h deleted file mode 100644 index dfee979e73..0000000000 --- a/userspace/libsinsp/sinsp_filtercheck_mesos.h +++ /dev/null @@ -1,54 +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 "sinsp_filtercheck.h" - -class sinsp_filter_check_mesos : public sinsp_filter_check -{ -public: - enum check_type - { - TYPE_MESOS_TASK_NAME = 0, - TYPE_MESOS_TASK_ID, - TYPE_MESOS_TASK_LABEL, - TYPE_MESOS_TASK_LABELS, - TYPE_MESOS_FRAMEWORK_NAME, - TYPE_MESOS_FRAMEWORK_ID, - TYPE_MARATHON_APP_NAME, - TYPE_MARATHON_APP_ID, - TYPE_MARATHON_APP_LABEL, - TYPE_MARATHON_APP_LABELS, - TYPE_MARATHON_GROUP_NAME, - TYPE_MARATHON_GROUP_ID, - }; - - sinsp_filter_check_mesos(); - sinsp_filter_check* allocate_new(); - int32_t parse_field_name(const char* str, bool alloc_state, bool needed_for_filtering); - uint8_t* extract(sinsp_evt *evt, OUT uint32_t* len, bool sanitize_strings = true); - -private: - - int32_t extract_arg(const std::string& fldname, const std::string& val); - - std::string m_argname; - std::string m_tstr; -}; - diff --git a/userspace/libsinsp/test/filterchecks/containers.cpp b/userspace/libsinsp/test/filterchecks/containers.cpp new file mode 100644 index 0000000000..e114e21d46 --- /dev/null +++ b/userspace/libsinsp/test/filterchecks/containers.cpp @@ -0,0 +1,124 @@ +// 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. + +*/ + +// Containers are supported only without minimal build +#ifndef MINIMAL_BUILD +#include +TEST_F(sinsp_with_test_input, CONTAINER_FILTER_check_k8s_fields_presence) +{ + add_default_init_thread(); + open_inspector(); + auto evt = generate_random_event(); + ASSERT_TRUE(field_exists(evt, "container.k8s.pod.name")); + ASSERT_TRUE(field_exists(evt, "container.k8s.pod.id")); + ASSERT_TRUE(field_exists(evt, "container.k8s.pod.label[one.second.third]")); + ASSERT_TRUE(field_exists(evt, "container.k8s.pod.labels")); + ASSERT_TRUE(field_exists(evt, "container.k8s.ns.name")); + + // container.k8s.pod.label requires an argument with the `[ ]` syntax. + ASSERT_THROW(field_exists(evt, "container.k8s.pod.label.one"), sinsp_exception); + ASSERT_THROW(field_exists(evt, "container.k8s.pod.label"), sinsp_exception); + ASSERT_THROW(field_exists(evt, "container.k8s.pod.label."), sinsp_exception); + ASSERT_THROW(field_exists(evt, "container.k8s.pod.label[["), sinsp_exception); + ASSERT_THROW(field_exists(evt, "container.k8s.pod.label]]"), sinsp_exception); + ASSERT_NO_THROW(field_exists(evt, "container.k8s.pod.label[]")); + + // There are no containers in the container manager, so there shouldn't be values + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.name")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.id")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.label[one]")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.labels")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.ns.name")); +} + +TEST_F(sinsp_with_test_input, CONTAINER_FILTER_check_k8s_fields_value) +{ + add_default_init_thread(); + open_inspector(); + + std::string container_id = "fce2a82f930f"; + std::string container_name = "kind-control-plane"; + std::string pod_name = "nginx"; + std::string pod_uid = "5eaeeca9-2277-460b-a4bf-5a0783f6d49f"; + std::string pod_namespace = "default"; + std::map labels = {{"io.x-k8s.kind.cluster", "kind"}, + {"io.x-k8s.kind.role", "control-plane"}, + {"io.kubernetes.sandbox.id", container_id}, + {"io.kubernetes.pod.name", pod_name}, + {"io.kubernetes.pod.uid", pod_uid}, + {"io.kubernetes.pod.namespace", pod_namespace}, + {"sample", "nginx"}}; + + auto init_thread_info = m_inspector.get_thread_ref(INIT_TID).get(); + auto container_info = std::make_shared(); + container_info->m_id = container_id; + init_thread_info->m_container_id = container_id; + container_info->m_name = container_name; + container_info->m_type = CT_DOCKER; + container_info->m_lookup.set_status(sinsp_container_lookup::state::SUCCESSFUL); + container_info->m_labels = labels; + m_inspector.m_container_manager.add_container(container_info, init_thread_info); + + auto evt = generate_random_event(); + // basic filterchecks + ASSERT_EQ(get_field_as_string(evt, "container.id"), container_id); + ASSERT_EQ(get_field_as_string(evt, "container.name"), container_name); + + // k8s filterchecks + ASSERT_EQ(get_field_as_string(evt, "container.k8s.pod.name"), pod_name); + ASSERT_EQ(get_field_as_string(evt, "container.k8s.pod.id"), pod_uid); + ASSERT_EQ(get_field_as_string(evt, "container.k8s.pod.label[sample]"), "nginx"); + ASSERT_EQ(get_field_as_string(evt, "container.k8s.pod.labels"), + "(io.x-k8s.kind.cluster:kind,io.x-k8s.kind.role:control-plane,sample:nginx)"); + ASSERT_EQ(get_field_as_string(evt, "container.k8s.ns.name"), pod_namespace); +} + +TEST_F(sinsp_with_test_input, CONTAINER_FILTER_check_k8s_fields_with_no_labels) +{ + add_default_init_thread(); + open_inspector(); + + std::string container_id = "fce2a82f930f"; + std::string container_name = "kind-control-plane"; + std::string pod_name = "nginx"; + std::string pod_uid = "5eaeeca9-2277-460b-a4bf-5a0783f6d49f"; + std::string pod_namespace = "default"; + std::map labels = {{"sample", "nginx"}}; + + auto init_thread_info = m_inspector.get_thread_ref(INIT_TID).get(); + auto container_info = std::make_shared(); + container_info->m_id = container_id; + init_thread_info->m_container_id = container_id; + container_info->m_name = container_name; + container_info->m_type = CT_DOCKER; + container_info->m_lookup.set_status(sinsp_container_lookup::state::SUCCESSFUL); + container_info->m_labels = labels; + m_inspector.m_container_manager.add_container(container_info, init_thread_info); + + auto evt = generate_random_event(); + ASSERT_EQ(get_field_as_string(evt, "container.id"), container_id); + ASSERT_EQ(get_field_as_string(evt, "container.name"), container_name); + + // If we don't have the `io.kubernetes...` labels on the container we cannot obtain these values + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.name")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.id")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.label[one]")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.pod.labels")); + ASSERT_FALSE(field_has_value(evt, "container.k8s.ns.name")); +} +#endif diff --git a/userspace/libsinsp/test/sinsp_with_test_input.h b/userspace/libsinsp/test/sinsp_with_test_input.h index d69a6bf8e7..bec7a3e3f2 100644 --- a/userspace/libsinsp/test/sinsp_with_test_input.h +++ b/userspace/libsinsp/test/sinsp_with_test_input.h @@ -411,7 +411,17 @@ class sinsp_with_test_input : public ::testing::Test { throw sinsp_exception("The event class is NULL"); } - return flist.new_filter_check_from_fldname(field_name, &m_inspector, false) != nullptr; + auto new_fl = flist.new_filter_check_from_fldname(field_name, &m_inspector, false); + if(new_fl != nullptr) + { + // if we can create a filter check it means that the field exists + delete new_fl; + return true; + } + else + { + return false; + } } // Return true if `field_name` value is not NULL for this event.