diff --git a/userspace/libsinsp/cri.h b/userspace/libsinsp/cri.h index afb9769e0e3..7c80488b354 100644 --- a/userspace/libsinsp/cri.h +++ b/userspace/libsinsp/cri.h @@ -224,6 +224,21 @@ template class cri_interface */ grpc::Status get_container_status_resp(const std::string &container_id, typename api::ContainerStatusResponse &resp); + /** + * @brief make request and get PodSandboxStatusResponse + * @param pod_sandbox_id ID of the pod sandbox + * @param resp initialized api::PodSandboxStatusResponse of the pod sandbox + * @return grpc::Status + */ + grpc::Status get_pod_sandbox_status_resp(const std::string &pod_sandbox_id, typename api::PodSandboxStatusResponse &resp); + + /** + * @brief get image id info from CRI + * @param image_ref the image ref from container metadata + * @return image id if found, empty string otherwise + */ + std::string get_container_image_id(const std::string &image_ref); + /** * @brief thin wrapper around CRI gRPC ContainerStats call * @param container_id container ID @@ -265,7 +280,7 @@ template class cri_interface /** * @brief fill out container image information based on CRI response * @param status `status` field of the ContainerStatusResponse - * @param root Json::Value of status.info() at "info of the ContainerStatusResponse + * @param root Json::Value of status.info() at "info" of the ContainerStatusResponse * @param container the container info to fill out * @return true if successful */ @@ -283,7 +298,7 @@ template class cri_interface /** * @brief fill out container environment variables based on CRI response, valid for containerd only - * @param root Json::Value of status.info() at "info of the ContainerStatusResponse + * @param root Json::Value of status.info() at "info" of the ContainerStatusResponse * @param container the container info to fill out * @return true if successful * @@ -293,7 +308,7 @@ template class cri_interface /** * @brief fill out extra image info based on CRI response, valid for containerd only - * @param root Json::Value of status.info() at "info of the ContainerStatusResponse + * @param root Json::Value of status.info() at "info" of the ContainerStatusResponse * @param container the container info to fill out * @return true if successful * @@ -303,7 +318,7 @@ template class cri_interface /** * @brief fill out extra container info (e.g. resource limits) based on CRI response - * @param root Json::Value of status.info() at "info of the ContainerStatusResponse + * @param root Json::Value of status.info() at "info" of the ContainerStatusResponse * @param container the container info to fill out * @return true if successful */ @@ -311,7 +326,7 @@ template class cri_interface /** * @brief fill out extra container user info (e.g. configured uid) based on CRI response - * @param root Json::Value of status.info() at "info of the ContainerStatusResponse + * @param root Json::Value of status.info() at "info" of the ContainerStatusResponse * @param container the container info to fill out * @return true if successful * @@ -336,8 +351,8 @@ template class cri_interface bool parse_cri_labels(const typename api::PodSandboxStatus &status, sinsp_container_info &container); /** - * @brief fill out pod sandbox id - * @param root Json::Value of status.info() at "info of the ContainerStatusResponse + * @brief fill out pod sandbox id, only valid when used w/ ContainerStatusResponse, not PodSandboxStatusResponse + * @param root Json::Value of status.info() at "info" of the ContainerStatusResponse * @param container the container info to fill out * @return true if successful */ @@ -347,7 +362,7 @@ template class cri_interface /** * @brief fill out pod sandbox network info * @param status `status` field of the PodSandboxStatusResponse - * @param root Json::Value of status.info() at "info of the PodSandboxStatusResponse + * @param root Json::Value of status.info() at "info" of the PodSandboxStatusResponse * @param container the container info to fill out * @return true if successful */ @@ -364,23 +379,9 @@ template class cri_interface bool parse_cri_pod_sandbox_labels(const typename api::PodSandboxStatus &status, sinsp_container_info &container); /** - * @brief make request and get PodSandboxStatusResponse - * @param pod_sandbox_id ID of the pod sandbox - * @param resp initialized api::PodSandboxStatusResponse of the pod sandbox - * @return grpc::Status - */ - grpc::Status get_pod_sandbox_status_resp(const std::string &pod_sandbox_id, typename api::PodSandboxStatusResponse &resp); - - /** - * @brief get image id info from CRI - * @param image_ref the image ref from container metadata - * @return image id if found, empty string otherwise - */ - std::string get_container_image_id(const std::string &image_ref); - - /** - * @brief fill in container metadata using the CRI API - * @param key the async lookup key (includes container_id) + * @brief fill in container metadata using the CRI API (`containerd` and `cri-o` container runtimes). + * This is the main CRI parser calling each parse_* helper after making the respective CRI API call(s). + * @param key includes container_id, but container.m_id is used to make the CRI API calls * @param container the container info to fill * @return true on success, false on failure */ diff --git a/userspace/libsinsp/cri.hpp b/userspace/libsinsp/cri.hpp index 4d01804aa13..4d276fb4906 100644 --- a/userspace/libsinsp/cri.hpp +++ b/userspace/libsinsp/cri.hpp @@ -190,6 +190,7 @@ inline bool cri_interface::parse_cri_base(const typename api::PodSandboxSta // This is in Nanoseconds(in CRI API). Need to convert it to seconds. container.m_created_time = static_cast(status.created_at() / ONE_SECOND_IN_NS); container.m_pod_sandbox_id = container.m_full_id; + // Add the pod sandbox id as label to the container for backward compatibility container.m_labels["io.kubernetes.sandbox.id"] = container.m_full_id; return true; @@ -231,9 +232,10 @@ inline bool cri_interface::parse_cri_image(const typename api::ContainerSta if(image_name.empty() || strncmp(image_name.c_str(), "sha256", 6) == 0) { - /* Retrieve image_name from annotations as backup when image name may start with sha256 - or otherwise was not retrieved. Brute force try each schema we know of for containerd and crio container - runtimes. */ + /* Retrieve image_name from annotations as backup when image name may (still) start with sha256 + * or otherwise was not successfully retrieved. Brute force try each schema we know of for containerd + * and cri-o container runtimes. + */ if(!root.isNull()) { @@ -405,8 +407,13 @@ inline bool cri_interface::parse_cri_env(const Json::Value &root, sinsp_con template inline bool cri_interface::parse_cri_json_imageid_containerd(const Json::Value &root, sinsp_container_info &container) { + if(root.isNull()) + { + return false; + } + const Json::Value *image = nullptr; - if(!walk_down_json(info, &image, "config", "image", "image") || !image->isString()) + if(!walk_down_json(root, &image, "config", "image", "image") || !image->isString()) { return false; } @@ -428,6 +435,11 @@ inline bool cri_interface::parse_cri_json_imageid_containerd(const Json::Va template inline bool cri_interface::parse_cri_ext_container_info(const Json::Value &root, sinsp_container_info &container) { + if(root.isNull()) + { + return false; + } + const Json::Value *linux = nullptr; if(!walk_down_json(root, &linux, "runtimeSpec", "linux") || !linux->isObject()) { @@ -480,6 +492,11 @@ inline bool cri_interface::parse_cri_ext_container_info(const Json::Value & template inline bool cri_interface::parse_cri_user_info(const Json::Value &root, sinsp_container_info &container) { + if(root.isNull()) + { + return false; + } + const Json::Value *uid = nullptr; if(!walk_down_json(root, &uid, "runtimeSpec", "process", "user", "uid") || !uid->isInt()) { @@ -782,7 +799,7 @@ inline bool cri_interface::parse(const libsinsp::cgroup_limits::cgroup_limi * in k8s filterchecks. Now, we also store the pod sandbox labels in the container. * While this might seem redundant in cases where multiple containers exist in a pod, considering that the concurrent * number of containers on a node is typically capped at 100-300 and many pods contain only 1-3 containers, - * it doesn't add significant overhead. Moreover, these extra lookups have always been performed for container ips in the past + * it doesn't add significant overhead. Moreover, these extra lookups have always been performed for container ips in the past * and therefore are no new additions. */ typename api::PodSandboxStatusResponse pod_sandbox_status_resp; diff --git a/userspace/libsinsp/sinsp_filtercheck_k8s.cpp b/userspace/libsinsp/sinsp_filtercheck_k8s.cpp index 5e475d7142d..fd24f57aef7 100644 --- a/userspace/libsinsp/sinsp_filtercheck_k8s.cpp +++ b/userspace/libsinsp/sinsp_filtercheck_k8s.cpp @@ -234,6 +234,10 @@ uint8_t* sinsp_filter_check_k8s::extract(sinsp_evt *evt, OUT uint32_t* len, bool m_tstr.clear(); + // Note: All fields are retrieved from the CRI API calls aka as part of the container engine lookups. + // There is no interaction w/ the Kubernetes Server in any way to retrieve these fields. As alternative explore the new `k8smeta` plugin. + // Comments explain the origin of each field (either ContainerStatusResponse or PodSandboxStatusResponse CRI API call). + switch(m_field_id) { case TYPE_K8S_POD_NAME: @@ -277,24 +281,54 @@ uint8_t* sinsp_filter_check_k8s::extract(sinsp_evt *evt, OUT uint32_t* len, bool case TYPE_K8S_POD_LABEL: case TYPE_K8S_POD_LABELS: // Requires s_cri_extra_queries enabled, which is the default for Falco. - // Note that m_pod_sandbox_labels, while part of the container, is retrieved from an extra PodSandboxStatusResponse call, not the ContainerStatusResponse CRI API call. + // Note that m_pod_sandbox_labels, while part of the container struct, is retrieved from an extra PodSandboxStatusResponse call, not the ContainerStatusResponse CRI API call. { - if (m_field_id == TYPE_K8S_POD_LABEL && container_info->m_pod_sandbox_labels.count(m_argname) > 0) + sinsp_container_info::ptr_t sandbox_container_info; + if(container_info->m_pod_sandbox_cniresult.empty()) // more robust check than checking for empty labels + { + // Fallback: Retrieve PodSandboxStatusResponse fields stored in explicit pod sandbox container + sandbox_container_info = m_inspector->m_container_manager.get_container(container_info->m_pod_sandbox_id.substr(0, 12)); + } + if (m_field_id == TYPE_K8S_POD_LABEL) { - m_tstr = container_info->m_pod_sandbox_labels.at(m_argname); + if(sandbox_container_info && sandbox_container_info->m_pod_sandbox_labels.count(m_argname) > 0) // fallback + { + m_tstr = sandbox_container_info->m_pod_sandbox_labels.at(m_argname); + } + else if (container_info->m_pod_sandbox_labels.count(m_argname) > 0) + { + m_tstr = container_info->m_pod_sandbox_labels.at(m_argname); + } RETURN_EXTRACT_STRING(m_tstr); } else if (m_field_id == TYPE_K8S_POD_LABELS) { - concatenate_container_labels(container_info->m_pod_sandbox_labels, &m_tstr); + if(sandbox_container_info) // fallback + { + concatenate_container_labels(sandbox_container_info->m_pod_sandbox_labels, &m_tstr); + } else + { + concatenate_container_labels(container_info->m_pod_sandbox_labels, &m_tstr); + } RETURN_EXTRACT_STRING(m_tstr); } } break; case TYPE_K8S_POD_IP: // Requires s_cri_extra_queries enabled, which is the default for Falco. - // Note that m_pod_sandbox_labels, while part of the container, is retrieved from an extra PodSandboxStatusResponse call, not the ContainerStatusResponse CRI API call. - m_u32val = htonl(container_info->m_container_ip); + // Note that m_pod_sandbox_labels, while part of the container struct, is retrieved from an extra PodSandboxStatusResponse call, not the ContainerStatusResponse CRI API call. + if(container_info->m_pod_sandbox_cniresult.empty()) // more robust check than checking for 0 in m_container_ip + { + // Fallback: Retrieve PodSandboxStatusResponse fields stored in pod sandbox container + const sinsp_container_info::ptr_t sandbox_container_info = m_inspector->m_container_manager.get_container(container_info->m_pod_sandbox_id.substr(0, 12)); + if(sandbox_container_info) + { + m_u32val = htonl(sandbox_container_info->m_container_ip); + } + } else + { + m_u32val = htonl(container_info->m_container_ip); + } char addrbuff[100]; inet_ntop(AF_INET, &m_u32val, addrbuff, sizeof(addrbuff)); m_tstr = addrbuff; @@ -302,7 +336,16 @@ uint8_t* sinsp_filter_check_k8s::extract(sinsp_evt *evt, OUT uint32_t* len, bool break; case TYPE_K8S_POD_CNIRESULT: // Requires s_cri_extra_queries enabled, which is the default for Falco. - // Note that m_pod_sandbox_labels, while part of the container, is retrieved from an extra PodSandboxStatusResponse call, not the ContainerStatusResponse CRI API call. + // Note that m_pod_sandbox_labels, while part of the container struct, is retrieved from an extra PodSandboxStatusResponse call, not the ContainerStatusResponse CRI API call. + if(container_info->m_pod_sandbox_cniresult.empty()) + { + // Fallback: Retrieve PodSandboxStatusResponse fields stored in pod sandbox container + const sinsp_container_info::ptr_t sandbox_container_info = m_inspector->m_container_manager.get_container(container_info->m_pod_sandbox_id.substr(0, 12)); + if(sandbox_container_info) + { + RETURN_EXTRACT_STRING(sandbox_container_info->m_pod_sandbox_cniresult); + } + } RETURN_EXTRACT_STRING(container_info->m_pod_sandbox_cniresult); break; default: diff --git a/userspace/libsinsp/test/container_engine/container_parser_cri_containerd.ut.cpp b/userspace/libsinsp/test/container_engine/container_parser_cri_containerd.ut.cpp index f935a331540..65603b40cc3 100644 --- a/userspace/libsinsp/test/container_engine/container_parser_cri_containerd.ut.cpp +++ b/userspace/libsinsp/test/container_engine/container_parser_cri_containerd.ut.cpp @@ -689,6 +689,38 @@ TEST_F(sinsp_with_test_input, container_parser_cri_containerd) ASSERT_EQ(get_field_as_string(evt, "k8s.pod.labels"), "app:myapp, example-label/custom_one:mylabel"); ASSERT_EQ(get_field_as_string(evt, "k8s.pod.ip"), "10.244.0.2"); ASSERT_EQ(get_field_as_string(evt, "k8s.pod.cni.json"), "{\"bridge\":{\"IPConfigs\":null},\"eth0\":{\"IPConfigs\":[{\"Gateway\":\"10.244.0.1\",\"IP\":\"10.244.0.2\"}]}}"); + + + // + // Simulate unsuccessful simultaneous PodSandboxStatusResponse lookup and subsequent k8s filterchecks fallbacks + // + + container.m_pod_sandbox_cniresult.clear(); + container.m_container_ip = 0; + container.m_pod_sandbox_labels.clear(); + m_inspector.m_container_manager.replace_container(std::move(container_ptr)); + + std::shared_ptr sandbox_container_ptr = std::make_shared(); + sinsp_container_info &sandbox_container = *sandbox_container_ptr; + + sandbox_container.m_type = CT_CONTAINERD; + sandbox_container.m_id = "63060edc2d3a"; // truncated id extracted from cgroups for the sandbox container + sandbox_container.m_is_pod_sandbox = true; + res = cri_api_v1alpha2->parse_cri_base(resp_pod_sandbox_container, sandbox_container); + ASSERT_TRUE(res); + res = cri_api_v1alpha2->parse_cri_labels(resp_pod_sandbox_container, sandbox_container); + ASSERT_TRUE(res); + res = cri_api_v1alpha2->parse_cri_pod_sandbox_network(resp_pod_sandbox_container, root_pod_sandbox, sandbox_container); + ASSERT_TRUE(res); + res = cri_api_v1alpha2->parse_cri_pod_sandbox_labels(resp_pod_sandbox_container, sandbox_container); + ASSERT_TRUE(res); + m_inspector.m_container_manager.add_container(std::move(sandbox_container_ptr), nullptr); + + ASSERT_EQ(get_field_as_string(evt, "k8s.pod.label.example-label/custom_one"), "mylabel"); + ASSERT_EQ(get_field_as_string(evt, "k8s.pod.label[example-label/custom_one]"), "mylabel"); + ASSERT_EQ(get_field_as_string(evt, "k8s.pod.labels"), "app:myapp, example-label/custom_one:mylabel"); + ASSERT_EQ(get_field_as_string(evt, "k8s.pod.ip"), "10.244.0.2"); + ASSERT_EQ(get_field_as_string(evt, "k8s.pod.cni.json"), "{\"bridge\":{\"IPConfigs\":null},\"eth0\":{\"IPConfigs\":[{\"Gateway\":\"10.244.0.1\",\"IP\":\"10.244.0.2\"}]}}"); } TEST_F(sinsp_with_test_input, container_parser_cri_containerd_sandbox_container) @@ -712,7 +744,7 @@ TEST_F(sinsp_with_test_input, container_parser_cri_containerd_sandbox_container) container.m_type = CT_CONTAINERD; container.m_id = "63060edc2d3a"; // truncated id extracted from cgroups for the sandbox container - container.m_is_pod_sandbox = true; + container.m_is_pod_sandbox = true; auto res = cri_api_v1alpha2->parse_cri_base(resp_pod_sandbox_container, container); ASSERT_TRUE(res); res = cri_api_v1alpha2->parse_cri_labels(resp_pod_sandbox_container, container); @@ -721,7 +753,7 @@ TEST_F(sinsp_with_test_input, container_parser_cri_containerd_sandbox_container) ASSERT_TRUE(res); res = cri_api_v1alpha2->parse_cri_pod_sandbox_labels(resp_pod_sandbox_container, container); ASSERT_TRUE(res); - ASSERT_TRUE(container.m_is_pod_sandbox); + ASSERT_TRUE(container.m_is_pod_sandbox); // // Test sinsp filterchecks, similar to spawn_process_container test