From 2041f4b37639ade31e611311c64526a08112b41b Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Wed, 4 Sep 2024 14:48:24 +0000 Subject: [PATCH 1/7] update(userspace/libsinsp): add network,pid,ipc namespace information to container_info Signed-off-by: Lorenzo Susini --- userspace/libsinsp/container.cpp | 3 +++ userspace/libsinsp/container_info.h | 36 ++++++++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/userspace/libsinsp/container.cpp b/userspace/libsinsp/container.cpp index 325bf6c92a..f6f74a3c09 100644 --- a/userspace/libsinsp/container.cpp +++ b/userspace/libsinsp/container.cpp @@ -166,6 +166,9 @@ std::string sinsp_container_manager::container_to_json(const sinsp_container_inf container["imagetag"] = container_info.m_imagetag; container["imagedigest"] = container_info.m_imagedigest; container["privileged"] = container_info.m_privileged; + container["host_pid"] = container_info.m_host_pid; + container["host_network"] = container_info.m_host_network; + container["host_ipc"] = container_info.m_host_ipc; container["is_pod_sandbox"] = container_info.m_is_pod_sandbox; container["lookup_state"] = static_cast(container_info.get_lookup_status()); container["created_time"] = static_cast(container_info.m_created_time); diff --git a/userspace/libsinsp/container_info.h b/userspace/libsinsp/container_info.h index 90a9d14d5c..bc50e0f6b6 100644 --- a/userspace/libsinsp/container_info.h +++ b/userspace/libsinsp/container_info.h @@ -216,20 +216,25 @@ class sinsp_container_info { }; sinsp_container_info(sinsp_container_lookup &&lookup = sinsp_container_lookup()): - m_type(CT_UNKNOWN), - m_container_ip(0), - m_privileged(false), - m_memory_limit(0), - m_swap_limit(0), - m_cpu_shares(1024), - m_cpu_quota(0), - m_cpu_period(100000), - m_cpuset_cpu_count(0), - m_is_pod_sandbox(false), - m_lookup(std::move(lookup)), - m_container_user(""), - m_metadata_deadline(0), - m_size_rw_bytes(-1) {} + m_type(CT_UNKNOWN), + m_container_ip(0), + m_privileged(false), + m_host_pid(false), + m_host_network(false), + m_host_ipc(false), + m_memory_limit(0), + m_swap_limit(0), + m_cpu_shares(1024), + m_cpu_quota(0), + m_cpu_period(100000), + m_cpuset_cpu_count(0), + m_is_pod_sandbox(false), + m_lookup(std::move(lookup)), + m_container_user(""), + m_metadata_deadline(0), + m_size_rw_bytes(-1) + { + } void clear() { this->~sinsp_container_info(); @@ -265,6 +270,9 @@ class sinsp_container_info { std::string m_imagedigest; uint32_t m_container_ip; bool m_privileged; + bool m_host_pid; + bool m_host_network; + bool m_host_ipc; std::vector m_mounts; std::vector m_port_mappings; std::map m_labels; From f71ee192b2334248d17f81fd3b2b93a9a431149c Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Wed, 4 Sep 2024 14:49:41 +0000 Subject: [PATCH 2/7] update(userspace/libsinsp): retrieve network,pid,ipc namespace information from docker socket Signed-off-by: Lorenzo Susini --- .../container_engine/docker/async_source.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/userspace/libsinsp/container_engine/docker/async_source.cpp b/userspace/libsinsp/container_engine/docker/async_source.cpp index 7941f8a918..a9b0e6c790 100644 --- a/userspace/libsinsp/container_engine/docker/async_source.cpp +++ b/userspace/libsinsp/container_engine/docker/async_source.cpp @@ -823,6 +823,21 @@ bool docker_async_source::parse(const docker_lookup_request& request, if(!privileged.isNull() && privileged.isBool()) { container.m_privileged = privileged.asBool(); } + const Json::Value& host_pid = host_config_obj["PidMode"]; + if(!host_pid.isNull() && host_pid.isString() && host_pid.asString() == "host") + { + container.m_host_pid = true; + } + const Json::Value& host_net = host_config_obj["NetworkMode"]; + if(!host_net.isNull() && host_net.isString() && host_net.asString() == "host") + { + container.m_host_network = true; + } + const Json::Value& ipc_mode = host_config_obj["IpcMode"]; + if(!ipc_mode.isNull() && ipc_mode.isString() && ipc_mode.asString() == "host") + { + container.m_host_ipc = true; + } parse_json_mounts(root["Mounts"], container.m_mounts); From a9f815023629a2d4520a45a05d8c46a88374a42c Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Wed, 4 Sep 2024 14:51:48 +0000 Subject: [PATCH 3/7] update(userspace/libsinsp): retrieve network,pid,ipc namespace information from CRI runtimes Signed-off-by: Lorenzo Susini --- userspace/libsinsp/cri.h | 21 +++++++++++++++++-- userspace/libsinsp/cri.hpp | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/userspace/libsinsp/cri.h b/userspace/libsinsp/cri.h index d9ab8a4f14..003aaa101d 100644 --- a/userspace/libsinsp/cri.h +++ b/userspace/libsinsp/cri.h @@ -363,8 +363,25 @@ class cri_interface { * @return true if successful */ bool parse_cri_pod_sandbox_network(const typename api::PodSandboxStatus &status, - const Json::Value &root, - sinsp_container_info &container); + const Json::Value &root, + sinsp_container_info &container); + + /** + * @brief fill out pod sandbox network namespace mode info + * @param status `status` field of the PodSandboxStatusResponse + * @param container the container info to fill out + */ + void parse_cri_pod_sandbox_pid(const typename api::PodSandboxStatus &status, + sinsp_container_info &container); + + /** + * @brief fill out pod sandbox ipc namespace mode info + * @param status `status` field of the PodSandboxStatusResponse + * @param container the container info to fill out + */ + void parse_cri_pod_sandbox_ipc(const typename api::PodSandboxStatus &status, + sinsp_container_info &container); + ///////////////////////////// // Generic parsers helpers diff --git a/userspace/libsinsp/cri.hpp b/userspace/libsinsp/cri.hpp index c591ad997c..5361ff73a4 100644 --- a/userspace/libsinsp/cri.hpp +++ b/userspace/libsinsp/cri.hpp @@ -529,6 +529,11 @@ inline bool cri_interface::parse_cri_ext_container_info(const Json::Value & bool priv_found = false; const Json::Value *privileged = nullptr; + + // + // Privileged flag + // + // old containerd? if(walk_down_json(*linux, &privileged, "security_context", "privileged") && privileged->isBool()) { @@ -628,6 +633,14 @@ inline bool cri_interface::parse_cri_pod_sandbox_network( const typename api::PodSandboxStatus &status, const Json::Value &root, sinsp_container_info &container) { + // + // Pod network namespace mode + // + if (status.linux().namespaces().options().network() == api::NamespaceMode::NODE) + { + container.m_host_network = true; + } + // // Pod IP // @@ -699,6 +712,32 @@ inline bool cri_interface::parse_cri_pod_sandbox_network( return true; } +template +inline void cri_interface::parse_cri_pod_sandbox_pid(const typename api::PodSandboxStatus &status, + sinsp_container_info &container) +{ + // + // Pod pid namespace mode + // + if (status.linux().namespaces().options().pid() == api::NamespaceMode::NODE) + { + container.m_host_pid = true; + } +} + +template +inline void cri_interface::parse_cri_pod_sandbox_ipc(const typename api::PodSandboxStatus &status, + sinsp_container_info &container) +{ + // + // Pod ipc namespace mode + // + if (status.linux().namespaces().options().ipc() == api::NamespaceMode::NODE) + { + container.m_host_ipc = true; + } +} + /////////////////////////////////////////////////////////////////// // Main CRI parse entrypoint (make API calls and parse responses) /////////////////////////////////////////////////////////////////// @@ -739,6 +778,8 @@ inline bool cri_interface::parse(const libsinsp::cgroup_limits::cgroup_limi // elsewhere in the response and add them as labels parse_cri_labels(resp_pod_sandbox_container, container); parse_cri_pod_sandbox_network(resp_pod_sandbox_container, root_pod_sandbox, container); + parse_cri_pod_sandbox_pid(resp_pod_sandbox_container, container); + parse_cri_pod_sandbox_ipc(resp_pod_sandbox_container, container); parse_cri_pod_sandbox_labels(resp_pod_sandbox_container, container); return true; } else { @@ -829,6 +870,8 @@ inline bool cri_interface::parse(const libsinsp::cgroup_limits::cgroup_limi const auto root_pod_sandbox = get_info_jvalue(resp_pod_sandbox_container_info); // Add pod response network and labels to original container parse_cri_pod_sandbox_network(resp_pod_sandbox_container, root_pod_sandbox, container); + parse_cri_pod_sandbox_pid(resp_pod_sandbox_container, container); + parse_cri_pod_sandbox_ipc(resp_pod_sandbox_container, container); parse_cri_pod_sandbox_labels(resp_pod_sandbox_container, container); } From 68f78e57b7b1509b510d2d987c870d20b4aac689 Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Wed, 4 Sep 2024 14:52:50 +0000 Subject: [PATCH 4/7] update(userspace/libsinsp): parse and expose namespace info Signed-off-by: Lorenzo Susini --- userspace/libsinsp/parsers.cpp | 26 +- .../libsinsp/sinsp_filtercheck_container.cpp | 270 +++++------------- .../libsinsp/sinsp_filtercheck_container.h | 3 + 3 files changed, 91 insertions(+), 208 deletions(-) diff --git a/userspace/libsinsp/parsers.cpp b/userspace/libsinsp/parsers.cpp index b7af5bab59..24c4ca9607 100644 --- a/userspace/libsinsp/parsers.cpp +++ b/userspace/libsinsp/parsers.cpp @@ -4736,11 +4736,27 @@ void sinsp_parser::parse_container_json_evt(sinsp_evt *evt) { if(check_json_val_is_convertible(privileged, Json::booleanValue, "privileged")) { container_info->m_privileged = privileged.asBool(); } - const Json::Value &lookup_state = container["lookup_state"]; - if(check_json_val_is_convertible(lookup_state, Json::uintValue, "lookup_state")) { - container_info->set_lookup_status( - static_cast(lookup_state.asUInt())); - switch(container_info->get_lookup_status()) { + const Json::Value& host_pid = container["host_pid"]; + if(check_json_val_is_convertible(host_pid, Json::booleanValue, "host_pid")) + { + container_info->m_host_pid = host_pid.asBool(); + } + const Json::Value& host_network = container["host_network"]; + if(check_json_val_is_convertible(host_network, Json::booleanValue, "host_network")) + { + container_info->m_host_network = host_network.asBool(); + } + const Json::Value& host_ipc = container["host_ipc"]; + if(check_json_val_is_convertible(host_ipc, Json::booleanValue, "host_ipc")) + { + container_info->m_host_ipc = host_ipc.asBool(); + } + const Json::Value& lookup_state = container["lookup_state"]; + if(check_json_val_is_convertible(lookup_state, Json::uintValue, "lookup_state")) + { + container_info->set_lookup_status(static_cast(lookup_state.asUInt())); + switch(container_info->get_lookup_status()) + { case sinsp_container_lookup::state::STARTED: case sinsp_container_lookup::state::SUCCESSFUL: case sinsp_container_lookup::state::FAILED: diff --git a/userspace/libsinsp/sinsp_filtercheck_container.cpp b/userspace/libsinsp/sinsp_filtercheck_container.cpp index 3ea445c7a0..0f5e44d476 100644 --- a/userspace/libsinsp/sinsp_filtercheck_container.cpp +++ b/userspace/libsinsp/sinsp_filtercheck_container.cpp @@ -34,209 +34,35 @@ using namespace std; return (uint8_t *)(x).c_str(); \ } while(0) -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), e.g. 3ad7b26ded6d is extracted from " - "the Linux cgroups by Falco within the kernel. Consequently, this field is reliably " - "available and serves as the lookup key for Falco's synchronous or asynchronous requests " - "against the container runtime socket to retrieve all other 'container.*' information. " - "One important aspect to be aware of is that if the process occurs on the host, meaning " - "not in the container PID namespace, this field is set to a string called 'host'. In " - "Kubernetes, pod sandbox container processes can exist where `container.id` matches " - "`k8s.pod.sandbox_id`, lacking other 'container.*' details."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.full_id", - "Container ID", - "The full container ID, e.g. " - "3ad7b26ded6d8e7b23da7d48fe889434573036c27ae5a74837233de441c3601e. In contrast to " - "`container.id`, we enrich this field as part of the container engine enrichment. In " - "instances of userspace container engine lookup delays, this field may not be available " - "yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.name", - "Container Name", - "The container name. In instances of userspace container engine lookup delays, this field " - "may not be available yet. One important aspect to be aware of is that if the process " - "occurs on the host, meaning not in the container PID namespace, this field is set to a " - "string called 'host'."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.image", - "Image Name", - "The container image name (e.g. falcosecurity/falco:latest for docker). In instances of " - "userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.image.id", - "Image ID", - "The container image id (e.g. 6f7e2741b66b). In instances of userspace container engine " - "lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.type", - "Type", - "The container type, e.g. docker, cri-o, containerd etc."}, - {PT_BOOL, - EPF_NONE, - PF_NA, - "container.privileged", - "Privileged", - "'true' for containers running as privileged, 'false' otherwise. In instances of " - "userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.mounts", - "Mounts", - "A space-separated list of mount information. Each item in the list has the format " - "'source:dest:mode:rdrw:propagation'. In instances of userspace container engine lookup " - "delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_ARG_REQUIRED, - PF_NA, - "container.mount", - "Mount", - "Information about a single mount, specified by number (e.g. container.mount[0]) or mount " - "source (container.mount[/usr/local]). The pathname can be a glob " - "(container.mount[/usr/local/*]), in which case the first matching mount will be " - "returned. The information has the format 'source:dest:mode:rdrw:propagation'. If there " - "is no mount with the specified index or matching the provided source, returns the string " - "\"none\" instead of a NULL value. In instances of userspace container engine lookup " - "delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_ARG_REQUIRED, - PF_NA, - "container.mount.source", - "Mount Source", - "The mount source, specified by number (e.g. container.mount.source[0]) or mount " - "destination (container.mount.source[/host/lib/modules]). The pathname can be a glob. In " - "instances of userspace container engine lookup delays, this field may not be available " - "yet."}, - {PT_CHARBUF, - EPF_ARG_REQUIRED, - PF_NA, - "container.mount.dest", - "Mount Destination", - "The mount destination, specified by number (e.g. container.mount.dest[0]) or mount " - "source (container.mount.dest[/lib/modules]). The pathname can be a glob. In instances of " - "userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_ARG_REQUIRED, - PF_NA, - "container.mount.mode", - "Mount Mode", - "The mount mode, specified by number (e.g. container.mount.mode[0]) or mount source " - "(container.mount.mode[/usr/local]). The pathname can be a glob. In instances of " - "userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_ARG_REQUIRED, - PF_NA, - "container.mount.rdwr", - "Mount Read/Write", - "The mount rdwr value, specified by number (e.g. container.mount.rdwr[0]) or mount source " - "(container.mount.rdwr[/usr/local]). The pathname can be a glob. In instances of " - "userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_ARG_REQUIRED, - PF_NA, - "container.mount.propagation", - "Mount Propagation", - "The mount propagation value, specified by number (e.g. container.mount.propagation[0]) " - "or mount source (container.mount.propagation[/usr/local]). The pathname can be a glob. " - "In instances of userspace container engine lookup delays, this field may not be " - "available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.image.repository", - "Repository", - "The container image repository (e.g. falcosecurity/falco). In instances of userspace " - "container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.image.tag", - "Image Tag", - "The container image tag (e.g. stable, latest). In instances of userspace container " - "engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.image.digest", - "Registry Digest", - "The container image registry digest (e.g. " - "sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27). In instances of " - "userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.healthcheck", - "Health Check", - "The container's health check. Will be the null value (\"N/A\") if no healthcheck " - "configured, \"NONE\" if configured but explicitly not created, and the healthcheck " - "command line otherwise. In instances of userspace container engine lookup delays, this " - "field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.liveness_probe", - "Liveness", - "The container's liveness probe. Will be the null value (\"N/A\") if no liveness probe " - "configured, the liveness probe command line otherwise. In instances of userspace " - "container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, - EPF_NONE, - PF_NA, - "container.readiness_probe", - "Readiness", - "The container's readiness probe. Will be the null value (\"N/A\") if no readiness probe " - "configured, the readiness probe command line otherwise. In instances of userspace " - "container engine lookup delays, this field may not be available yet."}, - {PT_UINT64, - EPF_NONE, - PF_DEC, - "container.start_ts", - "Container start", - "Container start as epoch timestamp in nanoseconds based on proc.pidns_init_start_ts and " - "extracted in the kernel and not from the container runtime socket / container engine."}, - {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. In instances of userspace container engine lookup " - "delays, this field may not be available yet."}, - {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). In " - "instances of userspace container engine lookup delays, this field may not be available " - "yet."}, +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), e.g. 3ad7b26ded6d is extracted from the Linux cgroups by Falco within the kernel. Consequently, this field is reliably available and serves as the lookup key for Falco's synchronous or asynchronous requests against the container runtime socket to retrieve all other 'container.*' information. One important aspect to be aware of is that if the process occurs on the host, meaning not in the container PID namespace, this field is set to a string called 'host'. In Kubernetes, pod sandbox container processes can exist where `container.id` matches `k8s.pod.sandbox_id`, lacking other 'container.*' details."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.full_id", "Container ID", "The full container ID, e.g. 3ad7b26ded6d8e7b23da7d48fe889434573036c27ae5a74837233de441c3601e. In contrast to `container.id`, we enrich this field as part of the container engine enrichment. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.name", "Container Name", "The container name. In instances of userspace container engine lookup delays, this field may not be available yet. One important aspect to be aware of is that if the process occurs on the host, meaning not in the container PID namespace, this field is set to a string called 'host'."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image", "Image Name", "The container image name (e.g. falcosecurity/falco:latest for docker). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.id", "Image ID", "The container image id (e.g. 6f7e2741b66b). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.type", "Type", "The container type, e.g. docker, cri-o, containerd etc."}, + {PT_BOOL, EPF_NONE, PF_NA, "container.privileged", "Privileged", "'true' for containers running as privileged, 'false' otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.mounts", "Mounts", "A space-separated list of mount information. Each item in the list has the format 'source:dest:mode:rdrw:propagation'. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount", "Mount", "Information about a single mount, specified by number (e.g. container.mount[0]) or mount source (container.mount[/usr/local]). The pathname can be a glob (container.mount[/usr/local/*]), in which case the first matching mount will be returned. The information has the format 'source:dest:mode:rdrw:propagation'. If there is no mount with the specified index or matching the provided source, returns the string \"none\" instead of a NULL value. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.source", "Mount Source", "The mount source, specified by number (e.g. container.mount.source[0]) or mount destination (container.mount.source[/host/lib/modules]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.dest", "Mount Destination", "The mount destination, specified by number (e.g. container.mount.dest[0]) or mount source (container.mount.dest[/lib/modules]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.mode", "Mount Mode", "The mount mode, specified by number (e.g. container.mount.mode[0]) or mount source (container.mount.mode[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.rdwr", "Mount Read/Write", "The mount rdwr value, specified by number (e.g. container.mount.rdwr[0]) or mount source (container.mount.rdwr[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.propagation", "Mount Propagation", "The mount propagation value, specified by number (e.g. container.mount.propagation[0]) or mount source (container.mount.propagation[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.repository", "Repository", "The container image repository (e.g. falcosecurity/falco). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.tag", "Image Tag", "The container image tag (e.g. stable, latest). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.digest", "Registry Digest", "The container image registry digest (e.g. sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.healthcheck", "Health Check", "The container's health check. Will be the null value (\"N/A\") if no healthcheck configured, \"NONE\" if configured but explicitly not created, and the healthcheck command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.liveness_probe", "Liveness", "The container's liveness probe. Will be the null value (\"N/A\") if no liveness probe configured, the liveness probe command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.readiness_probe", "Readiness", "The container's readiness probe. Will be the null value (\"N/A\") if no readiness probe configured, the readiness probe command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_UINT64, EPF_NONE, PF_DEC, "container.start_ts", "Container start", "Container start as epoch timestamp in nanoseconds based on proc.pidns_init_start_ts and extracted in the kernel and not from the container runtime socket / container engine."}, + {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. In instances of userspace container engine lookup delays, this field may not be available yet."}, + {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). In instances of userspace container engine lookup delays, this field may not be available yet."}, + {PT_BOOL, EPF_NONE, PF_NA, "container.host_pid", "Host PID Namespace", "'true' if the process is running in the host PID namespace, 'false' otherwise."}, + {PT_BOOL, EPF_NONE, PF_NA, "container.host_network", "Host Network Namespace", "'true' if the process is running in the host network namespace, 'false' otherwise."}, + {PT_BOOL, EPF_NONE, PF_NA, "container.host_ipc", "Host IPC Namespace", "'true' if the process is running in the host IPC namespace, 'false' otherwise."}, }; sinsp_filter_check_container::sinsp_filter_check_container() { @@ -499,6 +325,44 @@ uint8_t *sinsp_filter_check_container::extract_single(sinsp_evt *evt, m_val.u32 = (container_info->m_privileged ? 1 : 0); } + RETURN_EXTRACT_VAR(m_val.u32); + break; + case TYPE_CONTAINER_HOST_PID: + case TYPE_CONTAINER_HOST_NETWORK: + case TYPE_CONTAINER_HOST_IPC: + if (tinfo->m_container_id.empty()) + { + return NULL; + } + else + { + if(!container_info) + { + return NULL; + } + + // Only return a true/false value for + // container types where we really know the + // host_pid status. // todo(loresuso): double check this + if (!is_docker_compatible(container_info->m_type)) + { + return NULL; + } + + if (m_field_id == TYPE_CONTAINER_HOST_NETWORK) + { + m_val.u32 = (container_info->m_host_network ? 1 : 0); + } + else if (m_field_id == TYPE_CONTAINER_HOST_IPC) + { + m_val.u32 = (container_info->m_host_ipc ? 1 : 0); + } + else if (m_field_id == TYPE_CONTAINER_HOST_PID) + { + m_val.u32 = (container_info->m_host_pid ? 1 : 0); + } + } + RETURN_EXTRACT_VAR(m_val.u32); break; case TYPE_CONTAINER_MOUNTS: diff --git a/userspace/libsinsp/sinsp_filtercheck_container.h b/userspace/libsinsp/sinsp_filtercheck_container.h index ee62a95b4c..cd4260551d 100644 --- a/userspace/libsinsp/sinsp_filtercheck_container.h +++ b/userspace/libsinsp/sinsp_filtercheck_container.h @@ -48,6 +48,9 @@ class sinsp_filter_check_container : public sinsp_filter_check { TYPE_CONTAINER_DURATION, TYPE_CONTAINER_IP_ADDR, TYPE_CONTAINER_CNIRESULT, + TYPE_CONTAINER_HOST_PID, + TYPE_CONTAINER_HOST_NETWORK, + TYPE_CONTAINER_HOST_IPC, }; sinsp_filter_check_container(); From 020230bb0f746304229e5a6c5992ed4a1a213653 Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Wed, 4 Sep 2024 14:53:07 +0000 Subject: [PATCH 5/7] test(userspace/libsinsp): test CRI namespace information parsing Signed-off-by: Lorenzo Susini --- .../container_parser_cri_crio.ut.cpp | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp b/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp index ec3d62ec7e..c506f949be 100644 --- a/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp +++ b/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp @@ -507,9 +507,9 @@ runtime::v1alpha2::PodSandboxStatusResponse get_default_cri_crio_pod_status_resp // "linux": { // "namespaces": { // "options": { - // "ipc": "POD", + // "ipc": "NODE", // "network": "POD", - // "pid": "CONTAINER", + // "pid": "NODE", // "targetId": "" // } // } @@ -537,6 +537,9 @@ runtime::v1alpha2::PodSandboxStatusResponse get_default_cri_crio_pod_status_resp status->set_created_at((uint64_t)1676262698000004577); // dummy status->mutable_metadata()->set_name("podsandbox1"); status->mutable_network()->set_ip("10.244.0.3"); + status->mutable_linux()->mutable_namespaces()->mutable_options()->set_ipc(runtime::v1alpha2::NamespaceMode::NODE); + status->mutable_linux()->mutable_namespaces()->mutable_options()->set_network(runtime::v1alpha2::NamespaceMode::POD); + status->mutable_linux()->mutable_namespaces()->mutable_options()->set_pid(runtime::v1alpha2::NamespaceMode::NODE); auto labels = status->mutable_labels(); (*labels)["app"] = "myapp"; (*labels)["example-label/custom_one"] = "mylabel"; @@ -632,6 +635,11 @@ TEST_F(sinsp_with_test_input, container_parser_cri_crio) { root_pod_sandbox, container); ASSERT_TRUE(res); + ASSERT_FALSE(container.m_host_network); + cri_api_v1alpha2->parse_cri_pod_sandbox_pid(resp_pod_sandbox_container, container); + ASSERT_TRUE(container.m_host_pid); + cri_api_v1alpha2->parse_cri_pod_sandbox_ipc(resp_pod_sandbox_container, container); + ASSERT_TRUE(container.m_host_ipc); res = cri_api_v1alpha2->parse_cri_pod_sandbox_labels(resp_pod_sandbox_container, container); ASSERT_TRUE(res); @@ -775,13 +783,12 @@ TEST_F(sinsp_with_test_input, container_parser_cri_crio) { ASSERT_EQ(get_field_as_string(evt, "container.image.digest"), "sha256:49ecc282021562c567a8159ef424a06cdd8637efdca5953de9794eafe29adcad"); ASSERT_EQ(get_field_as_string(evt, "container.ip"), "10.244.0.3"); - ASSERT_EQ(get_field_as_string(evt, "container.cni.json"), - "{\"cniVersion\":\"1.0.0\",\"interfaces\":[{\"name\":\"bridge\",\"mac\":\"ce:64:08:" - "76:88:6a\"},{\"name\":\"veth71b0e931\",\"mac\":\"72:b7:4f:bc:e4:a4\"},{\"name\":" - "\"eth0\",\"mac\":\"fe:06:00:f8:2f:4d\",\"sandbox\":\"/var/run/netns/" - "dec735d1-0e86-44c1-94e0-a102173334a4\"}],\"ips\":[{\"interface\":2,\"address\":\"10." - "244.0.3/16\",\"gateway\":\"10.244.0.1\"}],\"routes\":[{\"dst\":\"0.0.0.0/" - "0\",\"gw\":\"10.244.0.1\"}],\"dns\":{}}"); + + ASSERT_EQ(get_field_as_string(evt, "container.cni.json"), "{\"cniVersion\":\"1.0.0\",\"interfaces\":[{\"name\":\"bridge\",\"mac\":\"ce:64:08:76:88:6a\"},{\"name\":\"veth71b0e931\",\"mac\":\"72:b7:4f:bc:e4:a4\"},{\"name\":\"eth0\",\"mac\":\"fe:06:00:f8:2f:4d\",\"sandbox\":\"/var/run/netns/dec735d1-0e86-44c1-94e0-a102173334a4\"}],\"ips\":[{\"interface\":2,\"address\":\"10.244.0.3/16\",\"gateway\":\"10.244.0.1\"}],\"routes\":[{\"dst\":\"0.0.0.0/0\",\"gw\":\"10.244.0.1\"}],\"dns\":{}}"); + ASSERT_EQ(get_field_as_string(evt, "container.host_pid"), "true"); + ASSERT_EQ(get_field_as_string(evt, "container.host_network"), "false"); + ASSERT_EQ(get_field_as_string(evt, "container.host_ipc"), "true"); + ASSERT_EQ(get_field_as_string(evt, "k8s.ns.name"), "redhat.test.crio"); ASSERT_EQ(get_field_as_string(evt, "k8s.pod.name"), "podsandbox1"); From 3188e9eeea6d95368f6539b61275f6f92eb0fbce Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Mon, 14 Oct 2024 10:09:49 +0000 Subject: [PATCH 6/7] format: apply clang format to new container field impl Signed-off-by: Lorenzo Susini --- .../container_engine/docker/async_source.cpp | 9 +- userspace/libsinsp/container_info.h | 36 ++- userspace/libsinsp/cri.h | 15 +- userspace/libsinsp/cri.hpp | 21 +- userspace/libsinsp/parsers.cpp | 26 +- .../libsinsp/sinsp_filtercheck_container.cpp | 274 +++++++++++++++--- .../container_parser_cri_crio.ut.cpp | 28 +- 7 files changed, 293 insertions(+), 116 deletions(-) diff --git a/userspace/libsinsp/container_engine/docker/async_source.cpp b/userspace/libsinsp/container_engine/docker/async_source.cpp index a9b0e6c790..2d8d33ef81 100644 --- a/userspace/libsinsp/container_engine/docker/async_source.cpp +++ b/userspace/libsinsp/container_engine/docker/async_source.cpp @@ -824,18 +824,15 @@ bool docker_async_source::parse(const docker_lookup_request& request, container.m_privileged = privileged.asBool(); } const Json::Value& host_pid = host_config_obj["PidMode"]; - if(!host_pid.isNull() && host_pid.isString() && host_pid.asString() == "host") - { + if(!host_pid.isNull() && host_pid.isString() && host_pid.asString() == "host") { container.m_host_pid = true; } const Json::Value& host_net = host_config_obj["NetworkMode"]; - if(!host_net.isNull() && host_net.isString() && host_net.asString() == "host") - { + if(!host_net.isNull() && host_net.isString() && host_net.asString() == "host") { container.m_host_network = true; } const Json::Value& ipc_mode = host_config_obj["IpcMode"]; - if(!ipc_mode.isNull() && ipc_mode.isString() && ipc_mode.asString() == "host") - { + if(!ipc_mode.isNull() && ipc_mode.isString() && ipc_mode.asString() == "host") { container.m_host_ipc = true; } diff --git a/userspace/libsinsp/container_info.h b/userspace/libsinsp/container_info.h index bc50e0f6b6..163a3ebab8 100644 --- a/userspace/libsinsp/container_info.h +++ b/userspace/libsinsp/container_info.h @@ -216,25 +216,23 @@ class sinsp_container_info { }; sinsp_container_info(sinsp_container_lookup &&lookup = sinsp_container_lookup()): - m_type(CT_UNKNOWN), - m_container_ip(0), - m_privileged(false), - m_host_pid(false), - m_host_network(false), - m_host_ipc(false), - m_memory_limit(0), - m_swap_limit(0), - m_cpu_shares(1024), - m_cpu_quota(0), - m_cpu_period(100000), - m_cpuset_cpu_count(0), - m_is_pod_sandbox(false), - m_lookup(std::move(lookup)), - m_container_user(""), - m_metadata_deadline(0), - m_size_rw_bytes(-1) - { - } + m_type(CT_UNKNOWN), + m_container_ip(0), + m_privileged(false), + m_host_pid(false), + m_host_network(false), + m_host_ipc(false), + m_memory_limit(0), + m_swap_limit(0), + m_cpu_shares(1024), + m_cpu_quota(0), + m_cpu_period(100000), + m_cpuset_cpu_count(0), + m_is_pod_sandbox(false), + m_lookup(std::move(lookup)), + m_container_user(""), + m_metadata_deadline(0), + m_size_rw_bytes(-1) {} void clear() { this->~sinsp_container_info(); diff --git a/userspace/libsinsp/cri.h b/userspace/libsinsp/cri.h index 003aaa101d..6ee41949f6 100644 --- a/userspace/libsinsp/cri.h +++ b/userspace/libsinsp/cri.h @@ -363,25 +363,24 @@ class cri_interface { * @return true if successful */ bool parse_cri_pod_sandbox_network(const typename api::PodSandboxStatus &status, - const Json::Value &root, - sinsp_container_info &container); + const Json::Value &root, + sinsp_container_info &container); /** * @brief fill out pod sandbox network namespace mode info * @param status `status` field of the PodSandboxStatusResponse * @param container the container info to fill out */ - void parse_cri_pod_sandbox_pid(const typename api::PodSandboxStatus &status, - sinsp_container_info &container); - + void parse_cri_pod_sandbox_pid(const typename api::PodSandboxStatus &status, + sinsp_container_info &container); + /** * @brief fill out pod sandbox ipc namespace mode info * @param status `status` field of the PodSandboxStatusResponse * @param container the container info to fill out */ - void parse_cri_pod_sandbox_ipc(const typename api::PodSandboxStatus &status, - sinsp_container_info &container); - + void parse_cri_pod_sandbox_ipc(const typename api::PodSandboxStatus &status, + sinsp_container_info &container); ///////////////////////////// // Generic parsers helpers diff --git a/userspace/libsinsp/cri.hpp b/userspace/libsinsp/cri.hpp index 5361ff73a4..954aac5605 100644 --- a/userspace/libsinsp/cri.hpp +++ b/userspace/libsinsp/cri.hpp @@ -636,8 +636,7 @@ inline bool cri_interface::parse_cri_pod_sandbox_network( // // Pod network namespace mode // - if (status.linux().namespaces().options().network() == api::NamespaceMode::NODE) - { + if(status.linux().namespaces().options().network() == api::NamespaceMode::NODE) { container.m_host_network = true; } @@ -713,27 +712,25 @@ inline bool cri_interface::parse_cri_pod_sandbox_network( } template -inline void cri_interface::parse_cri_pod_sandbox_pid(const typename api::PodSandboxStatus &status, - sinsp_container_info &container) -{ +inline void cri_interface::parse_cri_pod_sandbox_pid( + const typename api::PodSandboxStatus &status, + sinsp_container_info &container) { // // Pod pid namespace mode // - if (status.linux().namespaces().options().pid() == api::NamespaceMode::NODE) - { + if(status.linux().namespaces().options().pid() == api::NamespaceMode::NODE) { container.m_host_pid = true; } } template -inline void cri_interface::parse_cri_pod_sandbox_ipc(const typename api::PodSandboxStatus &status, - sinsp_container_info &container) -{ +inline void cri_interface::parse_cri_pod_sandbox_ipc( + const typename api::PodSandboxStatus &status, + sinsp_container_info &container) { // // Pod ipc namespace mode // - if (status.linux().namespaces().options().ipc() == api::NamespaceMode::NODE) - { + if(status.linux().namespaces().options().ipc() == api::NamespaceMode::NODE) { container.m_host_ipc = true; } } diff --git a/userspace/libsinsp/parsers.cpp b/userspace/libsinsp/parsers.cpp index 24c4ca9607..4c5e825e15 100644 --- a/userspace/libsinsp/parsers.cpp +++ b/userspace/libsinsp/parsers.cpp @@ -4736,27 +4736,23 @@ void sinsp_parser::parse_container_json_evt(sinsp_evt *evt) { if(check_json_val_is_convertible(privileged, Json::booleanValue, "privileged")) { container_info->m_privileged = privileged.asBool(); } - const Json::Value& host_pid = container["host_pid"]; - if(check_json_val_is_convertible(host_pid, Json::booleanValue, "host_pid")) - { + const Json::Value &host_pid = container["host_pid"]; + if(check_json_val_is_convertible(host_pid, Json::booleanValue, "host_pid")) { container_info->m_host_pid = host_pid.asBool(); } - const Json::Value& host_network = container["host_network"]; - if(check_json_val_is_convertible(host_network, Json::booleanValue, "host_network")) - { + const Json::Value &host_network = container["host_network"]; + if(check_json_val_is_convertible(host_network, Json::booleanValue, "host_network")) { container_info->m_host_network = host_network.asBool(); } - const Json::Value& host_ipc = container["host_ipc"]; - if(check_json_val_is_convertible(host_ipc, Json::booleanValue, "host_ipc")) - { + const Json::Value &host_ipc = container["host_ipc"]; + if(check_json_val_is_convertible(host_ipc, Json::booleanValue, "host_ipc")) { container_info->m_host_ipc = host_ipc.asBool(); } - const Json::Value& lookup_state = container["lookup_state"]; - if(check_json_val_is_convertible(lookup_state, Json::uintValue, "lookup_state")) - { - container_info->set_lookup_status(static_cast(lookup_state.asUInt())); - switch(container_info->get_lookup_status()) - { + const Json::Value &lookup_state = container["lookup_state"]; + if(check_json_val_is_convertible(lookup_state, Json::uintValue, "lookup_state")) { + container_info->set_lookup_status( + static_cast(lookup_state.asUInt())); + switch(container_info->get_lookup_status()) { case sinsp_container_lookup::state::STARTED: case sinsp_container_lookup::state::SUCCESSFUL: case sinsp_container_lookup::state::FAILED: diff --git a/userspace/libsinsp/sinsp_filtercheck_container.cpp b/userspace/libsinsp/sinsp_filtercheck_container.cpp index 0f5e44d476..7e4a691ed5 100644 --- a/userspace/libsinsp/sinsp_filtercheck_container.cpp +++ b/userspace/libsinsp/sinsp_filtercheck_container.cpp @@ -34,35 +34,227 @@ using namespace std; return (uint8_t *)(x).c_str(); \ } while(0) -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), e.g. 3ad7b26ded6d is extracted from the Linux cgroups by Falco within the kernel. Consequently, this field is reliably available and serves as the lookup key for Falco's synchronous or asynchronous requests against the container runtime socket to retrieve all other 'container.*' information. One important aspect to be aware of is that if the process occurs on the host, meaning not in the container PID namespace, this field is set to a string called 'host'. In Kubernetes, pod sandbox container processes can exist where `container.id` matches `k8s.pod.sandbox_id`, lacking other 'container.*' details."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.full_id", "Container ID", "The full container ID, e.g. 3ad7b26ded6d8e7b23da7d48fe889434573036c27ae5a74837233de441c3601e. In contrast to `container.id`, we enrich this field as part of the container engine enrichment. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.name", "Container Name", "The container name. In instances of userspace container engine lookup delays, this field may not be available yet. One important aspect to be aware of is that if the process occurs on the host, meaning not in the container PID namespace, this field is set to a string called 'host'."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.image", "Image Name", "The container image name (e.g. falcosecurity/falco:latest for docker). In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.id", "Image ID", "The container image id (e.g. 6f7e2741b66b). In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.type", "Type", "The container type, e.g. docker, cri-o, containerd etc."}, - {PT_BOOL, EPF_NONE, PF_NA, "container.privileged", "Privileged", "'true' for containers running as privileged, 'false' otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.mounts", "Mounts", "A space-separated list of mount information. Each item in the list has the format 'source:dest:mode:rdrw:propagation'. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount", "Mount", "Information about a single mount, specified by number (e.g. container.mount[0]) or mount source (container.mount[/usr/local]). The pathname can be a glob (container.mount[/usr/local/*]), in which case the first matching mount will be returned. The information has the format 'source:dest:mode:rdrw:propagation'. If there is no mount with the specified index or matching the provided source, returns the string \"none\" instead of a NULL value. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.source", "Mount Source", "The mount source, specified by number (e.g. container.mount.source[0]) or mount destination (container.mount.source[/host/lib/modules]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.dest", "Mount Destination", "The mount destination, specified by number (e.g. container.mount.dest[0]) or mount source (container.mount.dest[/lib/modules]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.mode", "Mount Mode", "The mount mode, specified by number (e.g. container.mount.mode[0]) or mount source (container.mount.mode[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.rdwr", "Mount Read/Write", "The mount rdwr value, specified by number (e.g. container.mount.rdwr[0]) or mount source (container.mount.rdwr[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_ARG_REQUIRED, PF_NA, "container.mount.propagation", "Mount Propagation", "The mount propagation value, specified by number (e.g. container.mount.propagation[0]) or mount source (container.mount.propagation[/usr/local]). The pathname can be a glob. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.repository", "Repository", "The container image repository (e.g. falcosecurity/falco). In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.tag", "Image Tag", "The container image tag (e.g. stable, latest). In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.digest", "Registry Digest", "The container image registry digest (e.g. sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27). In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.healthcheck", "Health Check", "The container's health check. Will be the null value (\"N/A\") if no healthcheck configured, \"NONE\" if configured but explicitly not created, and the healthcheck command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.liveness_probe", "Liveness", "The container's liveness probe. Will be the null value (\"N/A\") if no liveness probe configured, the liveness probe command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.readiness_probe", "Readiness", "The container's readiness probe. Will be the null value (\"N/A\") if no readiness probe configured, the readiness probe command line otherwise. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_UINT64, EPF_NONE, PF_DEC, "container.start_ts", "Container start", "Container start as epoch timestamp in nanoseconds based on proc.pidns_init_start_ts and extracted in the kernel and not from the container runtime socket / container engine."}, - {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. In instances of userspace container engine lookup delays, this field may not be available yet."}, - {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). In instances of userspace container engine lookup delays, this field may not be available yet."}, - {PT_BOOL, EPF_NONE, PF_NA, "container.host_pid", "Host PID Namespace", "'true' if the process is running in the host PID namespace, 'false' otherwise."}, - {PT_BOOL, EPF_NONE, PF_NA, "container.host_network", "Host Network Namespace", "'true' if the process is running in the host network namespace, 'false' otherwise."}, - {PT_BOOL, EPF_NONE, PF_NA, "container.host_ipc", "Host IPC Namespace", "'true' if the process is running in the host IPC namespace, 'false' otherwise."}, +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), e.g. 3ad7b26ded6d is extracted from " + "the Linux cgroups by Falco within the kernel. Consequently, this field is reliably " + "available and serves as the lookup key for Falco's synchronous or asynchronous requests " + "against the container runtime socket to retrieve all other 'container.*' information. " + "One important aspect to be aware of is that if the process occurs on the host, meaning " + "not in the container PID namespace, this field is set to a string called 'host'. In " + "Kubernetes, pod sandbox container processes can exist where `container.id` matches " + "`k8s.pod.sandbox_id`, lacking other 'container.*' details."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.full_id", + "Container ID", + "The full container ID, e.g. " + "3ad7b26ded6d8e7b23da7d48fe889434573036c27ae5a74837233de441c3601e. In contrast to " + "`container.id`, we enrich this field as part of the container engine enrichment. In " + "instances of userspace container engine lookup delays, this field may not be available " + "yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.name", + "Container Name", + "The container name. In instances of userspace container engine lookup delays, this field " + "may not be available yet. One important aspect to be aware of is that if the process " + "occurs on the host, meaning not in the container PID namespace, this field is set to a " + "string called 'host'."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.image", + "Image Name", + "The container image name (e.g. falcosecurity/falco:latest for docker). In instances of " + "userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.image.id", + "Image ID", + "The container image id (e.g. 6f7e2741b66b). In instances of userspace container engine " + "lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.type", + "Type", + "The container type, e.g. docker, cri-o, containerd etc."}, + {PT_BOOL, + EPF_NONE, + PF_NA, + "container.privileged", + "Privileged", + "'true' for containers running as privileged, 'false' otherwise. In instances of " + "userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.mounts", + "Mounts", + "A space-separated list of mount information. Each item in the list has the format " + "'source:dest:mode:rdrw:propagation'. In instances of userspace container engine lookup " + "delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_ARG_REQUIRED, + PF_NA, + "container.mount", + "Mount", + "Information about a single mount, specified by number (e.g. container.mount[0]) or mount " + "source (container.mount[/usr/local]). The pathname can be a glob " + "(container.mount[/usr/local/*]), in which case the first matching mount will be " + "returned. The information has the format 'source:dest:mode:rdrw:propagation'. If there " + "is no mount with the specified index or matching the provided source, returns the string " + "\"none\" instead of a NULL value. In instances of userspace container engine lookup " + "delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_ARG_REQUIRED, + PF_NA, + "container.mount.source", + "Mount Source", + "The mount source, specified by number (e.g. container.mount.source[0]) or mount " + "destination (container.mount.source[/host/lib/modules]). The pathname can be a glob. In " + "instances of userspace container engine lookup delays, this field may not be available " + "yet."}, + {PT_CHARBUF, + EPF_ARG_REQUIRED, + PF_NA, + "container.mount.dest", + "Mount Destination", + "The mount destination, specified by number (e.g. container.mount.dest[0]) or mount " + "source (container.mount.dest[/lib/modules]). The pathname can be a glob. In instances of " + "userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_ARG_REQUIRED, + PF_NA, + "container.mount.mode", + "Mount Mode", + "The mount mode, specified by number (e.g. container.mount.mode[0]) or mount source " + "(container.mount.mode[/usr/local]). The pathname can be a glob. In instances of " + "userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_ARG_REQUIRED, + PF_NA, + "container.mount.rdwr", + "Mount Read/Write", + "The mount rdwr value, specified by number (e.g. container.mount.rdwr[0]) or mount source " + "(container.mount.rdwr[/usr/local]). The pathname can be a glob. In instances of " + "userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_ARG_REQUIRED, + PF_NA, + "container.mount.propagation", + "Mount Propagation", + "The mount propagation value, specified by number (e.g. container.mount.propagation[0]) " + "or mount source (container.mount.propagation[/usr/local]). The pathname can be a glob. " + "In instances of userspace container engine lookup delays, this field may not be " + "available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.image.repository", + "Repository", + "The container image repository (e.g. falcosecurity/falco). In instances of userspace " + "container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.image.tag", + "Image Tag", + "The container image tag (e.g. stable, latest). In instances of userspace container " + "engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.image.digest", + "Registry Digest", + "The container image registry digest (e.g. " + "sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27). In instances of " + "userspace container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.healthcheck", + "Health Check", + "The container's health check. Will be the null value (\"N/A\") if no healthcheck " + "configured, \"NONE\" if configured but explicitly not created, and the healthcheck " + "command line otherwise. In instances of userspace container engine lookup delays, this " + "field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.liveness_probe", + "Liveness", + "The container's liveness probe. Will be the null value (\"N/A\") if no liveness probe " + "configured, the liveness probe command line otherwise. In instances of userspace " + "container engine lookup delays, this field may not be available yet."}, + {PT_CHARBUF, + EPF_NONE, + PF_NA, + "container.readiness_probe", + "Readiness", + "The container's readiness probe. Will be the null value (\"N/A\") if no readiness probe " + "configured, the readiness probe command line otherwise. In instances of userspace " + "container engine lookup delays, this field may not be available yet."}, + {PT_UINT64, + EPF_NONE, + PF_DEC, + "container.start_ts", + "Container start", + "Container start as epoch timestamp in nanoseconds based on proc.pidns_init_start_ts and " + "extracted in the kernel and not from the container runtime socket / container engine."}, + {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. In instances of userspace container engine lookup " + "delays, this field may not be available yet."}, + {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). In " + "instances of userspace container engine lookup delays, this field may not be available " + "yet."}, + {PT_BOOL, + EPF_NONE, + PF_NA, + "container.host_pid", + "Host PID Namespace", + "'true' if the process is running in the host PID namespace, 'false' otherwise."}, + {PT_BOOL, + EPF_NONE, + PF_NA, + "container.host_network", + "Host Network Namespace", + "'true' if the process is running in the host network namespace, 'false' otherwise."}, + {PT_BOOL, + EPF_NONE, + PF_NA, + "container.host_ipc", + "Host IPC Namespace", + "'true' if the process is running in the host IPC namespace, 'false' otherwise."}, }; sinsp_filter_check_container::sinsp_filter_check_container() { @@ -330,35 +522,25 @@ uint8_t *sinsp_filter_check_container::extract_single(sinsp_evt *evt, case TYPE_CONTAINER_HOST_PID: case TYPE_CONTAINER_HOST_NETWORK: case TYPE_CONTAINER_HOST_IPC: - if (tinfo->m_container_id.empty()) - { + if(tinfo->m_container_id.empty()) { return NULL; - } - else - { - if(!container_info) - { + } else { + if(!container_info) { return NULL; } // Only return a true/false value for // container types where we really know the // host_pid status. // todo(loresuso): double check this - if (!is_docker_compatible(container_info->m_type)) - { + if(!is_docker_compatible(container_info->m_type)) { return NULL; } - if (m_field_id == TYPE_CONTAINER_HOST_NETWORK) - { + if(m_field_id == TYPE_CONTAINER_HOST_NETWORK) { m_val.u32 = (container_info->m_host_network ? 1 : 0); - } - else if (m_field_id == TYPE_CONTAINER_HOST_IPC) - { + } else if(m_field_id == TYPE_CONTAINER_HOST_IPC) { m_val.u32 = (container_info->m_host_ipc ? 1 : 0); - } - else if (m_field_id == TYPE_CONTAINER_HOST_PID) - { + } else if(m_field_id == TYPE_CONTAINER_HOST_PID) { m_val.u32 = (container_info->m_host_pid ? 1 : 0); } } diff --git a/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp b/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp index c506f949be..8b711fd741 100644 --- a/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp +++ b/userspace/libsinsp/test/container_engine/container_parser_cri_crio.ut.cpp @@ -537,9 +537,12 @@ runtime::v1alpha2::PodSandboxStatusResponse get_default_cri_crio_pod_status_resp status->set_created_at((uint64_t)1676262698000004577); // dummy status->mutable_metadata()->set_name("podsandbox1"); status->mutable_network()->set_ip("10.244.0.3"); - status->mutable_linux()->mutable_namespaces()->mutable_options()->set_ipc(runtime::v1alpha2::NamespaceMode::NODE); - status->mutable_linux()->mutable_namespaces()->mutable_options()->set_network(runtime::v1alpha2::NamespaceMode::POD); - status->mutable_linux()->mutable_namespaces()->mutable_options()->set_pid(runtime::v1alpha2::NamespaceMode::NODE); + status->mutable_linux()->mutable_namespaces()->mutable_options()->set_ipc( + runtime::v1alpha2::NamespaceMode::NODE); + status->mutable_linux()->mutable_namespaces()->mutable_options()->set_network( + runtime::v1alpha2::NamespaceMode::POD); + status->mutable_linux()->mutable_namespaces()->mutable_options()->set_pid( + runtime::v1alpha2::NamespaceMode::NODE); auto labels = status->mutable_labels(); (*labels)["app"] = "myapp"; (*labels)["example-label/custom_one"] = "mylabel"; @@ -635,11 +638,11 @@ TEST_F(sinsp_with_test_input, container_parser_cri_crio) { root_pod_sandbox, container); ASSERT_TRUE(res); - ASSERT_FALSE(container.m_host_network); - cri_api_v1alpha2->parse_cri_pod_sandbox_pid(resp_pod_sandbox_container, container); - ASSERT_TRUE(container.m_host_pid); - cri_api_v1alpha2->parse_cri_pod_sandbox_ipc(resp_pod_sandbox_container, container); - ASSERT_TRUE(container.m_host_ipc); + ASSERT_FALSE(container.m_host_network); + cri_api_v1alpha2->parse_cri_pod_sandbox_pid(resp_pod_sandbox_container, container); + ASSERT_TRUE(container.m_host_pid); + cri_api_v1alpha2->parse_cri_pod_sandbox_ipc(resp_pod_sandbox_container, container); + ASSERT_TRUE(container.m_host_ipc); res = cri_api_v1alpha2->parse_cri_pod_sandbox_labels(resp_pod_sandbox_container, container); ASSERT_TRUE(res); @@ -784,12 +787,17 @@ TEST_F(sinsp_with_test_input, container_parser_cri_crio) { "sha256:49ecc282021562c567a8159ef424a06cdd8637efdca5953de9794eafe29adcad"); ASSERT_EQ(get_field_as_string(evt, "container.ip"), "10.244.0.3"); - ASSERT_EQ(get_field_as_string(evt, "container.cni.json"), "{\"cniVersion\":\"1.0.0\",\"interfaces\":[{\"name\":\"bridge\",\"mac\":\"ce:64:08:76:88:6a\"},{\"name\":\"veth71b0e931\",\"mac\":\"72:b7:4f:bc:e4:a4\"},{\"name\":\"eth0\",\"mac\":\"fe:06:00:f8:2f:4d\",\"sandbox\":\"/var/run/netns/dec735d1-0e86-44c1-94e0-a102173334a4\"}],\"ips\":[{\"interface\":2,\"address\":\"10.244.0.3/16\",\"gateway\":\"10.244.0.1\"}],\"routes\":[{\"dst\":\"0.0.0.0/0\",\"gw\":\"10.244.0.1\"}],\"dns\":{}}"); + ASSERT_EQ(get_field_as_string(evt, "container.cni.json"), + "{\"cniVersion\":\"1.0.0\",\"interfaces\":[{\"name\":\"bridge\",\"mac\":\"ce:64:08:" + "76:88:6a\"},{\"name\":\"veth71b0e931\",\"mac\":\"72:b7:4f:bc:e4:a4\"},{\"name\":" + "\"eth0\",\"mac\":\"fe:06:00:f8:2f:4d\",\"sandbox\":\"/var/run/netns/" + "dec735d1-0e86-44c1-94e0-a102173334a4\"}],\"ips\":[{\"interface\":2,\"address\":\"10." + "244.0.3/16\",\"gateway\":\"10.244.0.1\"}],\"routes\":[{\"dst\":\"0.0.0.0/" + "0\",\"gw\":\"10.244.0.1\"}],\"dns\":{}}"); ASSERT_EQ(get_field_as_string(evt, "container.host_pid"), "true"); ASSERT_EQ(get_field_as_string(evt, "container.host_network"), "false"); ASSERT_EQ(get_field_as_string(evt, "container.host_ipc"), "true"); - ASSERT_EQ(get_field_as_string(evt, "k8s.ns.name"), "redhat.test.crio"); ASSERT_EQ(get_field_as_string(evt, "k8s.pod.name"), "podsandbox1"); ASSERT_EQ(get_field_as_string(evt, "k8s.pod.id"), "redhat-test-crio"); // legacy pod UID From aa264caf79a7e5329584e5706080e1a80965a2ec Mon Sep 17 00:00:00 2001 From: Lorenzo Susini Date: Tue, 15 Oct 2024 11:03:15 +0000 Subject: [PATCH 7/7] chore: address review comments Signed-off-by: Lorenzo Susini --- userspace/libsinsp/sinsp_filtercheck_container.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/userspace/libsinsp/sinsp_filtercheck_container.cpp b/userspace/libsinsp/sinsp_filtercheck_container.cpp index 7e4a691ed5..e0e0b177c3 100644 --- a/userspace/libsinsp/sinsp_filtercheck_container.cpp +++ b/userspace/libsinsp/sinsp_filtercheck_container.cpp @@ -242,19 +242,19 @@ static const filtercheck_field_info sinsp_filter_check_container_fields[] = { PF_NA, "container.host_pid", "Host PID Namespace", - "'true' if the process is running in the host PID namespace, 'false' otherwise."}, + "'true' if the container is running in the host PID namespace, 'false' otherwise."}, {PT_BOOL, EPF_NONE, PF_NA, "container.host_network", "Host Network Namespace", - "'true' if the process is running in the host network namespace, 'false' otherwise."}, + "'true' if the container is running in the host network namespace, 'false' otherwise."}, {PT_BOOL, EPF_NONE, PF_NA, "container.host_ipc", "Host IPC Namespace", - "'true' if the process is running in the host IPC namespace, 'false' otherwise."}, + "'true' if the container is running in the host IPC namespace, 'false' otherwise."}, }; sinsp_filter_check_container::sinsp_filter_check_container() { @@ -522,7 +522,7 @@ uint8_t *sinsp_filter_check_container::extract_single(sinsp_evt *evt, case TYPE_CONTAINER_HOST_PID: case TYPE_CONTAINER_HOST_NETWORK: case TYPE_CONTAINER_HOST_IPC: - if(tinfo->m_container_id.empty()) { + if(is_host) { return NULL; } else { if(!container_info) { @@ -531,7 +531,7 @@ uint8_t *sinsp_filter_check_container::extract_single(sinsp_evt *evt, // Only return a true/false value for // container types where we really know the - // host_pid status. // todo(loresuso): double check this + // host_pid, host_network, host_ipc status. if(!is_docker_compatible(container_info->m_type)) { return NULL; }