diff --git a/collector/CMakeLists.txt b/collector/CMakeLists.txt index 4b1ec9a2e1..adc11c1735 100644 --- a/collector/CMakeLists.txt +++ b/collector/CMakeLists.txt @@ -49,6 +49,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/lib/CollectorVersion.h.in ${CMAKE_CUR set(FALCO_DIR ${PROJECT_SOURCE_DIR}/../falcosecurity-libs) +add_subdirectory(container-plugin) + add_subdirectory(${PROJECT_SOURCE_DIR}/proto) include_directories(${PROJECT_SOURCE_DIR}/lib) diff --git a/collector/Makefile b/collector/Makefile index 1e9fa42fe4..2288e1466f 100644 --- a/collector/Makefile +++ b/collector/Makefile @@ -9,9 +9,9 @@ COLLECTOR_BIN_DIR = $(CMAKE_DIR)/collector LIBSINSP_BIN_DIR = $(CMAKE_DIR)/collector/EXCLUDE_FROM_DEFAULT_BUILD/libsinsp SRC_MOUNT_DIR = /tmp/collector -HDRS := $(wildcard lib/*.h) $(shell find $(BASE_PATH)/falcosecurity-libs/userspace -name '*.h') +HDRS := $(wildcard lib/*.h) $(wildcard container-plugin/*.h) $(shell find $(BASE_PATH)/falcosecurity-libs/userspace -name '*.h') -SRCS := $(wildcard lib/*.cpp) collector.cpp +SRCS := $(wildcard lib/*.cpp) $(wildcard container-plugin/*.cpp) collector.cpp COLLECTOR_BUILD_DEPS := $(HDRS) $(SRCS) $(shell find $(BASE_PATH)/falcosecurity-libs -name '*.h' -o -name '*.cpp' -o -name '*.c') @@ -37,8 +37,10 @@ cmake-build/collector: cmake-configure/collector $(COLLECTOR_BUILD_DEPS) container/bin/collector: cmake-build/collector mkdir -p container/bin + mkdir -p container/libs cp "$(COLLECTOR_BIN_DIR)/collector" container/bin/collector cp "$(COLLECTOR_BIN_DIR)/self-checks" container/bin/self-checks + cp "$(COLLECTOR_BIN_DIR)/collector-container-plugin.so" container/libs/collector-container-plugin.so .PHONY: collector collector: container/bin/collector txt-files diff --git a/collector/container-plugin/CMakeLists.txt b/collector/container-plugin/CMakeLists.txt new file mode 100644 index 0000000000..2ade8f5f25 --- /dev/null +++ b/collector/container-plugin/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(collector-container-plugin SHARED ContainerPlugin.cpp) + +set_target_properties(collector-container-plugin PROPERTIES + PREFIX "" + OUTPUT_NAME "collector-container-plugin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/collector" +) + +target_include_directories(collector-container-plugin PRIVATE + ${FALCO_DIR}/userspace + ${FALCO_DIR}/driver +) diff --git a/collector/container-plugin/ContainerID.h b/collector/container-plugin/ContainerID.h new file mode 100644 index 0000000000..5afe569081 --- /dev/null +++ b/collector/container-plugin/ContainerID.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +namespace collector::container_plugin { + +constexpr size_t kContainerIDLength = 64; +constexpr size_t kShortContainerIDLength = 12; + +inline std::optional ExtractContainerIDFromCgroup(std::string_view cgroup) { + const auto scope = cgroup.rfind(".scope"); + if (scope != std::string_view::npos) { + cgroup.remove_suffix(cgroup.size() - scope); + } + if (cgroup.size() < kContainerIDLength + 1) { + return {}; + } + const auto id_start = cgroup.size() - kContainerIDLength; + const char separator = cgroup[id_start - 1]; + if (separator != '/' && separator != '-' && separator != ':') { + return {}; + } + const std::string_view parent = cgroup.substr(0, id_start - 1); + constexpr std::string_view kConmonSuffix = "-conmon"; + if (parent.size() >= kConmonSuffix.size() && + parent.substr(parent.size() - kConmonSuffix.size()) == kConmonSuffix) { + return {}; + } + const std::string_view id = cgroup.substr(id_start); + if (!std::all_of(id.begin(), id.end(), [](char c) { return std::isxdigit(static_cast(c)); })) { + return {}; + } + return id.substr(0, kShortContainerIDLength); +} + +} // namespace collector::container_plugin diff --git a/collector/container-plugin/ContainerPlugin.cpp b/collector/container-plugin/ContainerPlugin.cpp new file mode 100644 index 0000000000..0cbaa6c89a --- /dev/null +++ b/collector/container-plugin/ContainerPlugin.cpp @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include + +#include + +#include "ContainerID.h" + +namespace { + +constexpr std::string_view kPluginName = "collector-container"; +constexpr std::string_view kPluginVersion = "0.1.0"; +constexpr std::string_view kHostContainerID = "host"; + +struct plugin_state { + std::string last_error; + ss_plugin_table_t* threads = nullptr; + ss_plugin_table_field_t* cgroups = nullptr; + ss_plugin_table_field_t* cgroup_path = nullptr; + ss_plugin_table_field_t* container_id = nullptr; + std::string extracted_container_id; + const char* extracted_container_id_ptr = nullptr; +}; + +std::string ContainerIDFromCgroup(std::string_view cgroup) { + return std::string(collector::container_plugin::ExtractContainerIDFromCgroup(cgroup).value_or(std::string_view{})); +} + +struct cgroup_iteration_state { + plugin_state* plugin; + ss_plugin_table_reader_vtable_ext* reader; + ss_plugin_table_t* cgroup_table; + std::string container_id; +}; + +ss_plugin_bool FindContainerID(ss_plugin_table_iterator_state_t* data, ss_plugin_table_entry_t* entry) { + auto* state = reinterpret_cast(data); + ss_plugin_state_data value{}; + if (state->reader->read_entry_field(state->cgroup_table, entry, state->plugin->cgroup_path, &value) != SS_PLUGIN_SUCCESS) { + return 0; + } + if (value.str != nullptr) { + state->container_id = ContainerIDFromCgroup(value.str); + } + return 1; +} + +ss_plugin_rc CacheContainerID(plugin_state* state, + ss_plugin_table_entry_t* thread, + ss_plugin_table_reader_vtable_ext* reader, + ss_plugin_table_writer_vtable_ext* writer) { + ss_plugin_state_data cgroups{}; + if (reader->read_entry_field(state->threads, thread, state->cgroups, &cgroups) != SS_PLUGIN_SUCCESS || + cgroups.table == nullptr) { + state->last_error = "failed to read thread cgroups"; + return SS_PLUGIN_FAILURE; + } + + cgroup_iteration_state iteration{state, reader, cgroups.table, {}}; + if (!reader->iterate_entries(cgroups.table, FindContainerID, + reinterpret_cast(&iteration))) { + state->last_error = "failed to inspect thread cgroups"; + return SS_PLUGIN_FAILURE; + } + + ss_plugin_state_data value{}; + const std::string id = iteration.container_id.empty() ? std::string(kHostContainerID) : iteration.container_id; + value.str = id.c_str(); + if (writer->write_entry_field(state->threads, thread, state->container_id, &value) != SS_PLUGIN_SUCCESS) { + state->last_error = "failed to cache thread container ID"; + return SS_PLUGIN_FAILURE; + } + return SS_PLUGIN_SUCCESS; +} + +struct thread_iteration_state { + plugin_state* plugin; + ss_plugin_table_reader_vtable_ext* reader; + ss_plugin_table_writer_vtable_ext* writer; +}; + +ss_plugin_bool CacheInitialThread(ss_plugin_table_iterator_state_t* data, ss_plugin_table_entry_t* entry) { + auto* state = reinterpret_cast(data); + return CacheContainerID(state->plugin, entry, state->reader, state->writer) == SS_PLUGIN_SUCCESS; +} + +} // namespace + +extern "C" const char* plugin_get_required_api_version() { + return PLUGIN_API_VERSION_STR; +} + +extern "C" const char* plugin_get_version() { + return kPluginVersion.data(); +} + +extern "C" const char* plugin_get_name() { + return kPluginName.data(); +} + +extern "C" const char* plugin_get_description() { + return "Caches Collector container IDs in Falco thread state"; +} + +extern "C" const char* plugin_get_contact() { + return "https://github.com/stackrox/collector"; +} + +extern "C" const char* plugin_get_required_event_schema_version(ss_plugin_t*) { + return "4.1.0"; +} + +extern "C" ss_plugin_t* plugin_init(const ss_plugin_init_input* input, ss_plugin_rc* rc) { + auto* state = new plugin_state; + *rc = SS_PLUGIN_FAILURE; + if (input == nullptr || input->tables == nullptr || input->tables->fields_ext == nullptr || + input->tables->reader_ext == nullptr || input->tables->writer_ext == nullptr) { + state->last_error = "Falco table API is unavailable"; + return reinterpret_cast(state); + } + + state->threads = input->tables->get_table(input->owner, "threads", SS_PLUGIN_ST_INT64); + if (state->threads == nullptr) { + state->last_error = "failed to access Falco threads table"; + return reinterpret_cast(state); + } + state->cgroups = input->tables->fields_ext->get_table_field(state->threads, "cgroups", SS_PLUGIN_ST_TABLE); + state->container_id = input->tables->fields_ext->add_table_field(state->threads, "container_id", SS_PLUGIN_ST_STRING); + if (state->cgroups == nullptr || state->container_id == nullptr) { + state->last_error = "failed to access Falco thread cgroup or container ID fields"; + return reinterpret_cast(state); + } + + ss_plugin_table_entry_t* entry = input->tables->writer_ext->create_table_entry(state->threads); + ss_plugin_state_data cgroups{}; + if (entry == nullptr || input->tables->reader_ext->read_entry_field(state->threads, entry, state->cgroups, &cgroups) != SS_PLUGIN_SUCCESS || + cgroups.table == nullptr) { + if (entry != nullptr) { + input->tables->writer_ext->destroy_table_entry(state->threads, entry); + } + state->last_error = "failed to access Falco cgroup table"; + return reinterpret_cast(state); + } + state->cgroup_path = input->tables->fields_ext->get_table_field(cgroups.table, "second", SS_PLUGIN_ST_STRING); + input->tables->writer_ext->destroy_table_entry(state->threads, entry); + if (state->cgroup_path == nullptr) { + state->last_error = "failed to access Falco cgroup path field"; + return reinterpret_cast(state); + } + + *rc = SS_PLUGIN_SUCCESS; + return reinterpret_cast(state); +} + +extern "C" void plugin_destroy(ss_plugin_t* plugin) { + delete reinterpret_cast(plugin); +} + +extern "C" const char* plugin_get_last_error(ss_plugin_t* plugin) { + return reinterpret_cast(plugin)->last_error.c_str(); +} + +extern "C" const char* plugin_get_parse_event_sources() { + return "[\"syscall\"]"; +} + +extern "C" uint16_t* plugin_get_parse_event_types(uint32_t* count, ss_plugin_t*) { + static uint16_t event_types[] = { + PPME_SYSCALL_CLONE_20_X, + PPME_SYSCALL_FORK_20_X, + PPME_SYSCALL_VFORK_20_X, + PPME_SYSCALL_CLONE3_X, + PPME_SYSCALL_EXECVE_16_X, + PPME_SYSCALL_EXECVE_17_X, + PPME_SYSCALL_EXECVE_18_X, + PPME_SYSCALL_EXECVE_19_X, + PPME_SYSCALL_EXECVEAT_X, + PPME_SYSCALL_CHROOT_X, + }; + *count = sizeof(event_types) / sizeof(event_types[0]); + return event_types; +} + +extern "C" const char* plugin_get_fields() { + return R"([{"type":"string","name":"container.id","desc":"Cached container ID for the event thread"}])"; +} + +extern "C" const char* plugin_get_extract_event_sources() { + return "[\"syscall\"]"; +} + +extern "C" ss_plugin_rc plugin_extract_fields(ss_plugin_t* plugin, + const ss_plugin_event_input* event, + const ss_plugin_field_extract_input* input) { + auto* state = reinterpret_cast(plugin); + ss_plugin_state_data key{}; + key.s64 = static_cast(event->evt->tid); + ss_plugin_table_entry_t* thread = input->table_reader_ext->get_table_entry(state->threads, &key); + if (thread == nullptr) { + for (uint32_t i = 0; i < input->num_fields; ++i) { + input->fields[i].res_len = 0; + } + return SS_PLUGIN_SUCCESS; + } + + ss_plugin_state_data value{}; + const ss_plugin_rc read_rc = input->table_reader_ext->read_entry_field(state->threads, thread, state->container_id, &value); + input->table_reader_ext->release_table_entry(state->threads, thread); + if (read_rc != SS_PLUGIN_SUCCESS || value.str == nullptr) { + state->last_error = "failed to read cached thread container ID"; + return SS_PLUGIN_FAILURE; + } + + state->extracted_container_id = value.str; + state->extracted_container_id_ptr = state->extracted_container_id.c_str(); + for (uint32_t i = 0; i < input->num_fields; ++i) { + if (input->fields[i].field_id != 0) { + input->fields[i].res_len = 0; + continue; + } + input->fields[i].res.str = &state->extracted_container_id_ptr; + input->fields[i].res_len = 1; + } + return SS_PLUGIN_SUCCESS; +} + +extern "C" ss_plugin_rc plugin_parse_event(ss_plugin_t* plugin, + const ss_plugin_event_input* event, + const ss_plugin_event_parse_input* input) { + auto* state = reinterpret_cast(plugin); + ss_plugin_state_data key{}; + key.s64 = static_cast(event->evt->tid); + ss_plugin_table_entry_t* thread = input->table_reader_ext->get_table_entry(state->threads, &key); + if (thread == nullptr) { + return SS_PLUGIN_SUCCESS; + } + const ss_plugin_rc rc = CacheContainerID(state, thread, input->table_reader_ext, input->table_writer_ext); + input->table_reader_ext->release_table_entry(state->threads, thread); + return rc; +} + +extern "C" ss_plugin_rc plugin_capture_open(ss_plugin_t* plugin, const ss_plugin_capture_listen_input* input) { + auto* state = reinterpret_cast(plugin); + thread_iteration_state iteration{state, input->table_reader_ext, input->table_writer_ext}; + if (!input->table_reader_ext->iterate_entries( + state->threads, CacheInitialThread, + reinterpret_cast(&iteration))) { + if (state->last_error.empty()) { + state->last_error = "failed to cache initial thread container IDs"; + } + return SS_PLUGIN_FAILURE; + } + return SS_PLUGIN_SUCCESS; +} + +extern "C" ss_plugin_rc plugin_capture_close(ss_plugin_t*, const ss_plugin_capture_listen_input*) { + return SS_PLUGIN_SUCCESS; +} diff --git a/collector/container/Dockerfile b/collector/container/Dockerfile index 3cf7945a6e..e2c88c4e29 100644 --- a/collector/container/Dockerfile +++ b/collector/container/Dockerfile @@ -39,6 +39,7 @@ COPY container/THIRD_PARTY_NOTICES/ /THIRD_PARTY_NOTICES/ COPY kernel-modules /kernel-modules COPY container/bin/collector /usr/local/bin/ COPY container/bin/self-checks /usr/local/bin/self-checks +COPY container/libs/collector-container-plugin.so /usr/local/lib/collector/collector-container-plugin.so COPY container/status-check.sh /usr/local/bin/status-check.sh EXPOSE 8080 9090 diff --git a/collector/container/dev.Dockerfile b/collector/container/dev.Dockerfile index 148fd8bbd1..6fe4958c7a 100644 --- a/collector/container/dev.Dockerfile +++ b/collector/container/dev.Dockerfile @@ -23,6 +23,7 @@ COPY container/THIRD_PARTY_NOTICES/ /THIRD_PARTY_NOTICES/ COPY kernel-modules /kernel-modules COPY container/bin/collector /usr/local/bin/ COPY container/bin/self-checks /usr/local/bin/self-checks +COPY container/libs/collector-container-plugin.so /usr/local/lib/collector/collector-container-plugin.so COPY container/status-check.sh /usr/local/bin/status-check.sh EXPOSE 8080 9090 diff --git a/collector/container/konflux.Dockerfile b/collector/container/konflux.Dockerfile index 02086c988b..583d6cb48e 100644 --- a/collector/container/konflux.Dockerfile +++ b/collector/container/konflux.Dockerfile @@ -135,6 +135,7 @@ COPY --from=package_installer /out/ / COPY --from=builder ${CMAKE_BUILD_DIR}/collector/collector /usr/local/bin/ COPY --from=builder ${CMAKE_BUILD_DIR}/collector/self-checks /usr/local/bin/ +COPY --from=builder ${CMAKE_BUILD_DIR}/collector/collector-container-plugin.so /usr/local/lib/collector/collector-container-plugin.so COPY LICENSE /licenses/LICENSE diff --git a/collector/lib/CollectorConfig.cpp b/collector/lib/CollectorConfig.cpp index 294e25bb4d..1f22f26e0f 100644 --- a/collector/lib/CollectorConfig.cpp +++ b/collector/lib/CollectorConfig.cpp @@ -82,6 +82,7 @@ PathEnvVar tls_client_cert_path("ROX_COLLECTOR_TLS_CLIENT_CERT"); PathEnvVar tls_client_key_path("ROX_COLLECTOR_TLS_CLIENT_KEY"); BoolEnvVar disable_process_arguments("ROX_COLLECTOR_NO_PROCESS_ARGUMENTS", false); +StringEnvVar container_plugin_path("ROX_COLLECTOR_CONTAINER_PLUGIN_PATH", "/usr/local/lib/collector/collector-container-plugin.so"); } // namespace constexpr bool CollectorConfig::kTurnOffScrape; @@ -97,6 +98,7 @@ CollectorConfig::CollectorConfig() { scrape_interval_ = kScrapeInterval; turn_off_scrape_ = kTurnOffScrape; collection_method_ = kCollectionMethod; + container_plugin_path_ = container_plugin_path.value(); } void CollectorConfig::InitCollectorConfig(CollectorArgs* args) { diff --git a/collector/lib/CollectorConfig.h b/collector/lib/CollectorConfig.h index 2a5244c29e..6172d5c4fa 100644 --- a/collector/lib/CollectorConfig.h +++ b/collector/lib/CollectorConfig.h @@ -161,6 +161,7 @@ class CollectorConfig { unsigned int GetSinspTotalBufferSize() const { return sinsp_total_buffer_size_; } unsigned int GetSinspThreadCacheSize() const { return sinsp_thread_cache_size_; } bool DisableProcessArguments() const { return disable_process_arguments_; } + const std::string& ContainerPluginPath() const { return container_plugin_path_; } static std::pair CheckConfiguration(const char* config, Json::Value* root); @@ -225,6 +226,7 @@ class CollectorConfig { std::optional grpc_server_; bool disable_process_arguments_ = false; + std::string container_plugin_path_ = "/usr/local/lib/collector/collector-container-plugin.so"; // One ring buffer will be initialized for this many CPUs unsigned int sinsp_cpu_per_buffer_ = 0; diff --git a/collector/lib/Process.cpp b/collector/lib/Process.cpp index 4f6a305b51..738cb3058f 100644 --- a/collector/lib/Process.cpp +++ b/collector/lib/Process.cpp @@ -32,8 +32,8 @@ const std::shared_ptr ProcessStore::Fetch(uint64_t pid) { std::string Process::container_id() const { WaitForProcessInfo(); - if (system_inspector_threadinfo_) { - auto id = GetContainerID(*system_inspector_threadinfo_); + if (system_inspector_ && system_inspector_threadinfo_) { + auto id = GetContainerID(*system_inspector_->GetInspector(), *system_inspector_threadinfo_); if (!id.empty()) { return id; } @@ -98,6 +98,7 @@ Process::Process( ProcessStore::MapRef cache, system_inspector::Service* instance) : pid_(pid), + system_inspector_(instance), cache_(cache), process_info_pending_resolution_(false), system_inspector_callback_( diff --git a/collector/lib/Process.h b/collector/lib/Process.h index 8fb346984d..bb458d01f8 100644 --- a/collector/lib/Process.h +++ b/collector/lib/Process.h @@ -76,6 +76,7 @@ class Process : public IProcess { static const std::string NOT_AVAILABLE; // = "N/A" uint64_t pid_; + system_inspector::Service* system_inspector_; // A cache we are referenced from. Remove ourselves upon deletion. ProcessStore::MapRef cache_; diff --git a/collector/lib/ProcessSignalFormatter.cpp b/collector/lib/ProcessSignalFormatter.cpp index 17a4fa8e9f..8295b6fc7f 100644 --- a/collector/lib/ProcessSignalFormatter.cpp +++ b/collector/lib/ProcessSignalFormatter.cpp @@ -242,7 +242,7 @@ ProcessSignal* ProcessSignalFormatter::CreateProcessSignal(sinsp_threadinfo* tin signal->set_allocated_time(timestamp); // set container_id - signal->set_container_id(GetContainerID(*tinfo)); + signal->set_container_id(GetContainerID(*inspector_, *tinfo)); // set process lineage std::vector lineage; @@ -348,7 +348,7 @@ void ProcessSignalFormatter::GetProcessLineage(sinsp_threadinfo* tinfo, // all platforms. // if (pt->m_vpid == 0) { - if (GetContainerID(*pt).empty()) { + if (GetContainerID(*inspector_, *pt).empty()) { return false; } } else if (pt->m_pid == pt->m_vpid) { diff --git a/collector/lib/Utility.cpp b/collector/lib/Utility.cpp index 20e021d99e..efa1d7b7a5 100644 --- a/collector/lib/Utility.cpp +++ b/collector/lib/Utility.cpp @@ -25,6 +25,8 @@ extern "C" { #include "Logging.h" #include "Utility.h" +#include "../container-plugin/ContainerID.h" + namespace collector { static constexpr int kMsgBufSize = 4096; @@ -57,13 +59,17 @@ const char* SignalName(int signum) { } } -std::string GetContainerID(const sinsp_threadinfo& tinfo) { - for (const auto& [subsys, cgroup_path] : tinfo.cgroups()) { - if (auto id = ExtractContainerIDFromCgroup(cgroup_path)) { - return std::string(*id); - } +std::string GetContainerID(sinsp& inspector, const sinsp_threadinfo& tinfo) { + const auto& fields = inspector.m_thread_manager->dynamic_fields()->fields(); + const auto field = fields.find("container_id"); + if (field == fields.end()) { + return {}; } - return {}; + auto accessor = field->second.new_accessor(); + std::string container_id; + // libsinsp's dynamic-field read API is not const-qualified. + const_cast(tinfo).get_dynamic_field(accessor, container_id); + return container_id == "host" ? std::string{} : container_id; } std::string GetContainerID(sinsp_evt* event) { @@ -74,12 +80,16 @@ std::string GetContainerID(sinsp_evt* event) { if (!tinfo) { return {}; } - return GetContainerID(*tinfo); + sinsp* inspector = event->get_inspector(); + if (!inspector) { + return {}; + } + return GetContainerID(*inspector, *tinfo); } std::ostream& operator<<(std::ostream& os, const sinsp_threadinfo* t) { if (t) { - os << "Container: \"" << GetContainerID(*t) << "\", Name: " << t->m_comm << ", PID: " << t->m_pid << ", Args: " << t->m_exe; + os << "Name: " << t->m_comm << ", PID: " << t->m_pid << ", Args: " << t->m_exe; } else { os << "NULL\n"; } @@ -195,50 +205,8 @@ void TryUnlink(const char* path) { } } -const static unsigned int CONTAINER_ID_LENGTH = 64; -const static unsigned int SHORT_CONTAINER_ID_LENGTH = 12; - -// IsContainerID returns whether the given string view represents a container ID. -bool IsContainerID(std::string_view str) { - if (str.size() != CONTAINER_ID_LENGTH) { - return false; - } - - return std::all_of(str.begin(), str.end(), [](char c) -> bool { - return std::isxdigit(c) != 0; - }); -} - std::optional ExtractContainerIDFromCgroup(std::string_view cgroup) { - if (cgroup.size() < CONTAINER_ID_LENGTH + 1) { - return {}; - } - - auto scope = cgroup.rfind(".scope"); - if (scope != std::string_view::npos) { - cgroup.remove_suffix(cgroup.length() - scope); - if (cgroup.size() < CONTAINER_ID_LENGTH + 1) { - return {}; - } - } - - auto container_id_part = cgroup.substr(cgroup.size() - (CONTAINER_ID_LENGTH + 1)); - if (container_id_part[0] != '/' && container_id_part[0] != '-' && container_id_part[0] != ':') { - return {}; - } - - cgroup.remove_suffix(CONTAINER_ID_LENGTH + 1); - // conmon runs as its own container, we ignore it. - if (cgroup.find("-conmon", cgroup.size() - StrLen("-conmon")) != std::string_view::npos) { - return {}; - } - - container_id_part.remove_prefix(1); - - if (!IsContainerID(container_id_part)) { - return {}; - } - return std::make_optional(container_id_part.substr(0, SHORT_CONTAINER_ID_LENGTH)); + return container_plugin::ExtractContainerIDFromCgroup(cgroup); } std::optional SanitizedUTF8(std::string_view str) { diff --git a/collector/lib/Utility.h b/collector/lib/Utility.h index f9ba73126b..193a08e19a 100644 --- a/collector/lib/Utility.h +++ b/collector/lib/Utility.h @@ -15,6 +15,7 @@ // forward declarations class sinsp_threadinfo; class sinsp_evt; +class sinsp; namespace collector { @@ -66,9 +67,9 @@ std::string Str(Args&&... args) { std::ostream& operator<<(std::ostream& os, const sinsp_threadinfo* t); -// Extract container ID from a threadinfo's cgroups. -// Returns an empty string if no container ID found. -std::string GetContainerID(const sinsp_threadinfo& tinfo); +// Return the cached container ID from a threadinfo. +// Returns an empty string for host processes. +std::string GetContainerID(sinsp& inspector, const sinsp_threadinfo& tinfo); // Extract container ID from an event's thread info cgroups. // Returns an empty string if no container ID found. diff --git a/collector/lib/system-inspector/Service.cpp b/collector/lib/system-inspector/Service.cpp index 9985bdce68..6150d5ac75 100644 --- a/collector/lib/system-inspector/Service.cpp +++ b/collector/lib/system-inspector/Service.cpp @@ -6,7 +6,9 @@ #include +#include "libsinsp/filter.h" #include "libsinsp/parsers.h" +#include "libsinsp/plugin.h" #include "libsinsp/sinsp.h" #include @@ -29,6 +31,9 @@ namespace collector::system_inspector { +namespace { +} // namespace + Service::~Service() = default; Service::Service(const CollectorConfig& config) @@ -46,6 +51,12 @@ Service::Service(const CollectorConfig& config) inspector_->disable_log_timestamps(); inspector_->set_log_callback(logging::InspectorLogCallback); + container_plugin_ = inspector_->register_plugin(config.ContainerPluginPath()); + std::string plugin_error; + if (!container_plugin_->init("{}", plugin_error)) { + CLOG(FATAL) << "Failed to initialise container plugin: " << plugin_error; + } + inspector_->set_import_users(config.ImportUsers()); inspector_->set_thread_timeout_s(30); inspector_->set_auto_threads_purging_interval_s(60); @@ -58,36 +69,6 @@ Service::Service(const CollectorConfig& config) inspector_->get_parser()->set_track_connection_status(true); } - // Filter out host processes to avoid flooding Sensor with events it - // cannot associate with a container. The filter has two clauses: - // - // 1. pid != vpid — In a PID namespace (the common container case), - // the kernel PID differs from the virtual PID visible inside the - // container. The val() transformer makes the parser treat - // proc.vpid as a field reference instead of a literal string. - // - // 2. cgroup regex — Catches containers that share the host PID - // namespace (hostPID: true), where pid == vpid despite the - // process running inside a container. Container runtimes always - // place container processes in a cgroup whose path ends with the - // 64-hex-character container ID, so matching that pattern - // identifies containerised processes regardless of PID namespace - // configuration. This mirrors the cgroup-based container ID - // extraction in ExtractContainerIDFromCgroup(). - // - // The memory cgroup is used because on cgroups v2 with systemd, - // the memory controller is reliably delegated to the container's - // leaf cgroup (where the path contains the container ID), while - // cpuset is often only available at a higher level in the - // hierarchy. The trailing (/.*) accounts for additional path - // components some runtimes append (e.g. podman adds /container). - // - // The 'or' short-circuits: the regex only evaluates for events where - // the PID check fails, so the performance cost is negligible. - inspector_->set_filter( - "proc.pid != val(proc.vpid)" - " or thread.cgroup.memory regex \".*[/:-][0-9a-f]{64}(\\\\.scope)?(/.*)?\""); - // The self-check handlers should only operate during start up, // so they are added to the handler list first, so they have access // to self-check events before the network and process handlers have @@ -119,6 +100,13 @@ bool Service::InitKernel(const CollectorConfig& config) { return false; } + sinsp_filter_check_list filter_list; + filter_list.add_filter_check(inspector_->new_generic_filtercheck()); + filter_list.add_filter_check(sinsp_plugin::new_filtercheck(container_plugin_)); + auto filter_factory = std::make_shared(inspector_.get(), filter_list); + sinsp_filter_compiler filter_compiler(filter_factory, "container.id != host"); + inspector_->set_filter(filter_compiler.compile(), "container.id != host"); + return true; } @@ -174,25 +162,14 @@ sinsp_evt* Service::GetNext() { bool Service::FilterEvent(sinsp_evt* event) { const auto* tinfo = event->get_thread_info(); - return FilterEvent(tinfo); + return FilterEvent(*event->get_inspector(), tinfo); } -bool Service::FilterEvent(const sinsp_threadinfo* tinfo) { +bool Service::FilterEvent(sinsp&, const sinsp_threadinfo* tinfo) { if (tinfo == nullptr) { return false; } - // Exclude host processes that leak through the sinsp filter. - // The sinsp filter uses a cgroup regex to catch containers in - // the host PID namespace, but this can also match container - // runtime helpers (crun, runc, conmon, podman) that run on the - // host within cgroup paths containing container IDs. Checking - // GetContainerID catches all such cases without maintaining a - // list of runtime helper names. - if (GetContainerID(*tinfo).empty()) { - return false; - } - std::string_view exepath_sv{tinfo->m_exepath}; auto marker = exepath_sv.rfind(':'); if (marker != std::string_view::npos) { @@ -300,7 +277,7 @@ bool Service::SendExistingProcesses(SignalHandler* handler) { } return threads->loop([&](sinsp_threadinfo& tinfo) { - if (!GetContainerID(tinfo).empty() && tinfo.is_main_thread()) { + if (!GetContainerID(*inspector_, tinfo).empty() && tinfo.is_main_thread()) { auto result = handler->HandleExistingProcess(&tinfo); if (result == SignalHandler::ERROR || result == SignalHandler::NEEDS_REFRESH) { CLOG(WARNING) << "Failed to write existing process signal: " << &tinfo; diff --git a/collector/lib/system-inspector/Service.h b/collector/lib/system-inspector/Service.h index 1f2398c648..915b330820 100644 --- a/collector/lib/system-inspector/Service.h +++ b/collector/lib/system-inspector/Service.h @@ -17,6 +17,7 @@ class sinsp; class sinsp_evt; class sinsp_evt_formatter; +class sinsp_plugin; class sinsp_threadinfo; namespace collector::system_inspector { @@ -62,12 +63,13 @@ class Service : public SystemInspector { sinsp_evt* GetNext(); static bool FilterEvent(sinsp_evt* event); - static bool FilterEvent(const sinsp_threadinfo* tinfo); + static bool FilterEvent(sinsp& inspector, const sinsp_threadinfo* tinfo); bool SendExistingProcesses(SignalHandler* handler); mutable std::mutex libsinsp_mutex_; std::unique_ptr inspector_; + std::shared_ptr container_plugin_; std::unique_ptr default_formatter_; std::unique_ptr signal_client_; std::vector signal_handlers_; diff --git a/collector/test/CMakeLists.txt b/collector/test/CMakeLists.txt index d4e9b12bda..956530b947 100644 --- a/collector/test/CMakeLists.txt +++ b/collector/test/CMakeLists.txt @@ -20,6 +20,8 @@ foreach(test_file ${TEST_SRC_FILES}) endif() add_test(${test_name} ${test_name}) + add_dependencies(${test_name} collector-container-plugin) + set_property(TEST ${test_name} APPEND PROPERTY ENVIRONMENT "ROX_COLLECTOR_CONTAINER_PLUGIN_PATH=${CMAKE_BINARY_DIR}/collector/collector-container-plugin.so") if(USE_VALGRIND) # TODO: This test has a deadlock when running on valgrind. Further investigation needed. diff --git a/collector/test/SystemInspectorServiceTest.cpp b/collector/test/SystemInspectorServiceTest.cpp index c2745cdbbc..ea5a1dac85 100644 --- a/collector/test/SystemInspectorServiceTest.cpp +++ b/collector/test/SystemInspectorServiceTest.cpp @@ -1,5 +1,10 @@ +#include + +#include +#include #include +#include "Utility.h" #include "gtest/gtest.h" #include "system-inspector/Service.h" @@ -7,31 +12,33 @@ namespace collector::system_inspector { TEST(SystemInspectorServiceTest, FilterEvent) { std::unique_ptr inspector(new sinsp()); - const auto& factory = inspector->get_threadinfo_factory(); + const char* plugin_path = std::getenv("ROX_COLLECTOR_CONTAINER_PLUGIN_PATH"); + ASSERT_NE(plugin_path, nullptr); + auto plugin = inspector->register_plugin(plugin_path); + std::string error; + ASSERT_TRUE(plugin->init("{}", error)) << error; + sinsp_filter_check_list filter_list; + filter_list.add_filter_check(inspector->new_generic_filtercheck()); + filter_list.add_filter_check(sinsp_plugin::new_filtercheck(plugin)); + auto filter_factory = std::make_shared(inspector.get(), filter_list); + sinsp_filter_compiler filter_compiler(filter_factory, "container.id != host"); + ASSERT_NO_THROW(filter_compiler.compile()); - // A container cgroup path with a 64-hex-char container ID. - const std::string container_cgroup = "/kubepods/burstable/pod123/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - sinsp_threadinfo::cgroups_t container_cgroups = {{"memory", container_cgroup}}; + const auto& fields = inspector->m_thread_manager->dynamic_fields()->fields(); + const auto container_id_field = fields.find("container_id"); + ASSERT_NE(container_id_field, fields.end()); + const auto container_id_accessor = container_id_field->second.new_accessor(); + const auto& factory = inspector->get_threadinfo_factory(); auto regular_process = factory.create(); regular_process->m_exepath = "/bin/busybox"; regular_process->m_comm = "sleep"; - regular_process->set_cgroups(container_cgroups); + regular_process->set_dynamic_field(container_id_accessor, std::string("aaaaaaaaaaaa")); auto runc_process = factory.create(); runc_process->m_exepath = "runc"; runc_process->m_comm = "6"; - auto proc_self_process = factory.create(); - proc_self_process->m_exepath = "/proc/self/exe"; - proc_self_process->m_comm = "6"; - proc_self_process->set_cgroups(container_cgroups); - - auto memfd_process = factory.create(); - memfd_process->m_exepath = "memfd:runc_cloned:/proc/self/exe"; - memfd_process->m_comm = "6"; - memfd_process->set_cgroups(container_cgroups); - auto host_process = factory.create(); host_process->m_exepath = "/usr/bin/bash"; host_process->m_comm = "bash"; @@ -43,16 +50,18 @@ TEST(SystemInspectorServiceTest, FilterEvent) { }; std::vector tests{ {regular_process.get(), true, "regular container process"}, - {runc_process.get(), false, "runc (no container ID)"}, - {proc_self_process.get(), false, "/proc/self exe path"}, - {memfd_process.get(), false, "memfd /proc/self exe path"}, - {host_process.get(), false, "host process (no container ID)"}, + {runc_process.get(), true, "runc process"}, + {host_process.get(), true, "host process"}, }; for (const auto& t : tests) { - ASSERT_EQ(system_inspector::Service::FilterEvent(t.tinfo), t.expected) + ASSERT_EQ(system_inspector::Service::FilterEvent(*inspector, t.tinfo), t.expected) << "Failed for: " << t.name; } + + EXPECT_EQ(GetContainerID(*inspector, *regular_process), "aaaaaaaaaaaa"); + regular_process->set_dynamic_field(container_id_accessor, std::string("host")); + EXPECT_TRUE(GetContainerID(*inspector, *regular_process).empty()); } } // namespace collector::system_inspector