diff --git a/userspace/libsinsp/CMakeLists.txt b/userspace/libsinsp/CMakeLists.txt index 556c01a22e5..16909f1ad7e 100644 --- a/userspace/libsinsp/CMakeLists.txt +++ b/userspace/libsinsp/CMakeLists.txt @@ -257,17 +257,45 @@ function(prepare_containerd_grpc) ${CMAKE_CURRENT_SOURCE_DIR}/container_engine/containerd/containers.proto ${DEST}/containers.proto COPYONLY ) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/container_engine/containerd/images.proto ${DEST}/images.proto + COPYONLY + ) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/container_engine/containerd/descriptor.proto + ${DEST}/descriptor.proto COPYONLY + ) add_custom_command( - OUTPUT ${DEST}/containers.grpc.pb.cc ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.cc + OUTPUT ${DEST}/containers.grpc.pb.cc + ${DEST}/containers.grpc.pb.h + ${DEST}/containers.pb.cc ${DEST}/containers.pb.h + ${DEST}/images.grpc.pb.cc + ${DEST}/images.grpc.pb.h + ${DEST}/images.pb.cc + ${DEST}/images.pb.h + ${DEST}/descriptor.grpc.pb.cc + ${DEST}/descriptor.grpc.pb.h + ${DEST}/descriptor.pb.cc + ${DEST}/descriptor.pb.h COMMENT "Generate containerd grpc code" DEPENDS COMMAND ${PROTOC} -I ${DEST} --cpp_out=${DEST} ${DEST}/containers.proto COMMAND ${PROTOC} -I ${DEST} --grpc_out=. --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${DEST}/containers.proto + COMMAND ${PROTOC} -I ${DEST} --cpp_out=${DEST} ${DEST}/descriptor.proto + COMMAND ${PROTOC} -I ${DEST} --grpc_out=. --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} + ${DEST}/descriptor.proto + COMMAND ${PROTOC} -I ${DEST} --cpp_out=${DEST} ${DEST}/images.proto + COMMAND ${PROTOC} -I ${DEST} --grpc_out=. --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} + ${DEST}/images.proto WORKING_DIRECTORY ${DEST} ) - add_library(containerd_interface STATIC ${DEST}/containers.pb.cc ${DEST}/containers.grpc.pb.cc) + add_library( + containerd_interface STATIC + ${DEST}/containers.pb.cc ${DEST}/containers.grpc.pb.cc ${DEST}/descriptor.pb.cc + ${DEST}/descriptor.grpc.pb.cc ${DEST}/images.pb.cc ${DEST}/images.grpc.pb.cc + ) target_include_directories(containerd_interface PUBLIC $) target_link_libraries( containerd_interface @@ -281,7 +309,8 @@ function(prepare_containerd_grpc) ) add_dependencies(containerd_interface grpc) install( - FILES ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.h + FILES ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.h ${DEST}/descriptor.grpc.pb.h + ${DEST}/descriptor.pb.h ${DEST}/images.grpc.pb.h ${DEST}/images.pb.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIBS_PACKAGE_NAME}/libsinsp/container_engine/containerd" COMPONENT "scap" @@ -318,7 +347,7 @@ if(NOT WIN32) prepare_containerd_grpc() target_link_libraries(sinsp PUBLIC cri_v1alpha2 cri_v1) - target_link_libraries(sinsp PUBLIC containerd_interface) + target_link_libraries(sinsp PRIVATE containerd_interface) if(NOT MUSL_OPTIMIZED_BUILD) find_library(LIB_ANL anl) diff --git a/userspace/libsinsp/container_engine/containerd.cpp b/userspace/libsinsp/container_engine/containerd.cpp index 122aadab912..8a8719ed8f2 100644 --- a/userspace/libsinsp/container_engine/containerd.cpp +++ b/userspace/libsinsp/container_engine/containerd.cpp @@ -34,7 +34,19 @@ constexpr const std::string_view CONTAINERD_SOCKETS[] = { }; bool containerd_async_source::is_ok() { - return m_stub != nullptr; + return m_container_stub != nullptr && m_image_stub != nullptr; +} + +static inline void setup_grpc_client_context(grpc::ClientContext &context) { + auto deadline = std::chrono::system_clock::now() + + std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); + + context.set_deadline(deadline); + + // The `default` namesapce is the default one of containerd + // and the one used by host-containers in bottlerocket. + // This is mandatory to query the containers. + context.AddMetadata("containerd-namespace", "default"); } containerd_async_source::containerd_async_source(const std::string &socket_path, @@ -47,30 +59,50 @@ containerd_async_source::containerd_async_source(const std::string &socket_path, std::shared_ptr channel = libsinsp::grpc_channel_registry::get_channel("unix://" + socket_path, &args); - m_stub = ContainerdService::Containers::NewStub(channel); + // Check the status of the container stub. + { + grpc::ClientContext context; + setup_grpc_client_context(context); - ContainerdService::ListContainersRequest req; - ContainerdService::ListContainersResponse resp; + m_container_stub = ContainerdContainerService::Containers::NewStub(channel); - grpc::ClientContext context; - auto deadline = std::chrono::system_clock::now() + - std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); - context.set_deadline(deadline); + ContainerdContainerService::ListContainersRequest req; + ContainerdContainerService::ListContainersResponse resp; - // The `default` namesapce is the default one of containerd - // and the one used by host-containers in bottlerocket. - // This is mandatory to query the containers. - context.AddMetadata("containerd-namespace", "default"); - grpc::Status status = m_stub->List(&context, req, &resp); + grpc::Status status = m_container_stub->List(&context, req, &resp); - if(!status.ok()) { - libsinsp_logger()->format(sinsp_logger::SEV_NOTICE, - "containerd (%s): containerd runtime returned an error after " - "trying to list containerd: %s", - socket_path.c_str(), - status.error_message().c_str()); - m_stub.reset(nullptr); - return; + if(!status.ok()) { + libsinsp_logger()->format(sinsp_logger::SEV_NOTICE, + "containerd (%s): containerd runtime returned an error after " + "trying to list containers: %s", + socket_path.c_str(), + status.error_message().c_str()); + m_container_stub.reset(nullptr); + return; + } + } + + // Check the status of the image stub. + { + grpc::ClientContext context; + setup_grpc_client_context(context); + + m_image_stub = ContainerdImageService::Images::NewStub(channel); + + ContainerdImageService::ListImagesRequest req; + ContainerdImageService::ListImagesResponse resp; + + grpc::Status status = m_image_stub->List(&context, req, &resp); + + if(!status.ok()) { + libsinsp_logger()->format(sinsp_logger::SEV_NOTICE, + "containerd (%s): containerd runtime returned an error after " + "trying to list images: %s", + socket_path.c_str(), + status.error_message().c_str()); + m_image_stub.reset(nullptr); + return; + } } } @@ -81,8 +113,8 @@ containerd_async_source::~containerd_async_source() { grpc::Status containerd_async_source::list_container_resp( const std::string &container_id, - ContainerdService::ListContainersResponse &resp) { - ContainerdService::ListContainersRequest req; + ContainerdContainerService::ListContainersResponse &resp) { + ContainerdContainerService::ListContainersRequest req; // To match the container using a truncated containerd id // we need to use a match filter (~=). @@ -92,7 +124,21 @@ grpc::Status containerd_async_source::list_container_resp( auto deadline = std::chrono::system_clock::now() + std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); context.set_deadline(deadline); - return m_stub->List(&context, req, &resp); + return m_container_stub->List(&context, req, &resp); +} + +grpc::Status containerd_async_source::get_image_resp( + const std::string &image_name, + ContainerdImageService::GetImageResponse &resp) { + ContainerdImageService::GetImageRequest req; + + req.set_name(image_name); + grpc::ClientContext context; + context.AddMetadata("containerd-namespace", "default"); + auto deadline = std::chrono::system_clock::now() + + std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); + context.set_deadline(deadline); + return m_image_stub->Get(&context, req, &resp); } libsinsp::container_engine::containerd::containerd(container_cache_interface &cache): @@ -132,7 +178,7 @@ bool containerd_async_source::parse(const containerd_lookup_request &request, // given the truncated container id, the full container id needs to be retrivied from // containerd. - ContainerdService::ListContainersResponse resp; + ContainerdContainerService::ListContainersResponse resp; grpc::Status status = list_container_resp(container_id, resp); if(!status.ok()) { @@ -174,9 +220,23 @@ bool containerd_async_source::parse(const containerd_lookup_request &request, // and the first part is the repo container.m_imagerepo = raw_image_splits[0].substr(0, raw_image_splits[0].rfind("/")); container.m_imagetag = raw_image_splits[1]; - container.m_imagedigest = ""; container.m_type = CT_CONTAINERD; + // Retrieve the image digest. + ContainerdImageService::GetImageResponse img_resp; + status = get_image_resp(containers[0].image(), img_resp); + + if(!status.ok()) { + libsinsp_logger()->format(sinsp_logger::SEV_DEBUG, + "containerd (%s): GetImageResponse status error message: (%s)", + container.m_id.c_str(), + status.error_message().c_str()); + + // Don't exit given that we have part of the needed information. + } + + container.m_imagedigest = img_resp.image().target().digest(); + // Retrieve the labels. for(const auto &pair : containers[0].labels()) { if(pair.second.length() <= sinsp_container_info::m_container_label_max_length) { diff --git a/userspace/libsinsp/container_engine/containerd.h b/userspace/libsinsp/container_engine/containerd.h index e267800cc39..21818840590 100644 --- a/userspace/libsinsp/container_engine/containerd.h +++ b/userspace/libsinsp/container_engine/containerd.h @@ -22,11 +22,13 @@ class sinsp_container_info; class sinsp_threadinfo; #include +#include #include #include #include -namespace ContainerdService = containerd::services::containers::v1; +namespace ContainerdContainerService = containerd::services::containers::v1; +namespace ContainerdImageService = containerd::services::images::v1; namespace libsinsp { namespace container_engine { @@ -80,7 +82,6 @@ class containerd_async_source : public container_async_source m_stub; + grpc::Status get_image_resp(const std::string& image_name, + ContainerdImageService::GetImageResponse& resp); + + std::unique_ptr m_container_stub; + std::unique_ptr m_image_stub; std::string m_socket_path; }; diff --git a/userspace/libsinsp/container_engine/containerd/descriptor.proto b/userspace/libsinsp/container_engine/containerd/descriptor.proto new file mode 100644 index 00000000000..faaf416dd10 --- /dev/null +++ b/userspace/libsinsp/container_engine/containerd/descriptor.proto @@ -0,0 +1,33 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +// Descriptor describes a blob in a content store. +// +// This descriptor can be used to reference content from an +// oci descriptor found in a manifest. +// See https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1#Descriptor +message Descriptor { + string media_type = 1; + string digest = 2; + int64 size = 3; + map annotations = 5; +} diff --git a/userspace/libsinsp/container_engine/containerd/images.proto b/userspace/libsinsp/container_engine/containerd/images.proto new file mode 100644 index 00000000000..742caede20a --- /dev/null +++ b/userspace/libsinsp/container_engine/containerd/images.proto @@ -0,0 +1,149 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.services.images.v1; + +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/timestamp.proto"; +import "descriptor.proto"; + +option go_package = "github.com/containerd/containerd/api/services/images/v1;images"; + +// Images is a service that allows one to register images with containerd. +// +// In containerd, an image is merely the mapping of a name to a content root, +// described by a descriptor. The behavior and state of image is purely +// dictated by the type of the descriptor. +// +// From the perspective of this service, these references are mostly shallow, +// in that the existence of the required content won't be validated until +// required by consuming services. +// +// As such, this can really be considered a "metadata service". +service Images { + // Get returns an image by name. + rpc Get(GetImageRequest) returns (GetImageResponse); + + // List returns a list of all images known to containerd. + rpc List(ListImagesRequest) returns (ListImagesResponse); + + // Create an image record in the metadata store. + // + // The name of the image must be unique. + rpc Create(CreateImageRequest) returns (CreateImageResponse); + + // Update assigns the name to a given target image based on the provided + // image. + rpc Update(UpdateImageRequest) returns (UpdateImageResponse); + + // Delete deletes the image by name. + rpc Delete(DeleteImageRequest) returns (google.protobuf.Empty); +} + +message Image { + // Name provides a unique name for the image. + // + // Containerd treats this as the primary identifier. + string name = 1; + + // Labels provides free form labels for the image. These are runtime only + // and do not get inherited into the package image in any way. + // + // Labels may be updated using the field mask. + // The combined size of a key/value pair cannot exceed 4096 bytes. + map labels = 2; + + // Target describes the content entry point of the image. + containerd.types.Descriptor target = 3; + + // CreatedAt is the time the image was first created. + google.protobuf.Timestamp created_at = 7; + + // UpdatedAt is the last time the image was mutated. + google.protobuf.Timestamp updated_at = 8; +} + +message GetImageRequest { + string name = 1; +} + +message GetImageResponse { + Image image = 1; +} + +message CreateImageRequest { + Image image = 1; + + google.protobuf.Timestamp source_date_epoch = 2; +} + +message CreateImageResponse { + Image image = 1; +} + +message UpdateImageRequest { + // Image provides a full or partial image for update. + // + // The name field must be set or an error will be returned. + Image image = 1; + + // UpdateMask specifies which fields to perform the update on. If empty, + // the operation applies to all fields. + google.protobuf.FieldMask update_mask = 2; + + google.protobuf.Timestamp source_date_epoch = 3; +} + +message UpdateImageResponse { + Image image = 1; +} + +message ListImagesRequest { + // Filters contains one or more filters using the syntax defined in the + // containerd filter package. + // + // The returned result will be those that match any of the provided + // filters. Expanded, images that match the following will be + // returned: + // + // filters[0] or filters[1] or ... or filters[n-1] or filters[n] + // + // If filters is zero-length or nil, all items will be returned. + repeated string filters = 1; +} + +message ListImagesResponse { + repeated Image images = 1; +} + +message DeleteImageRequest { + string name = 1; + + // Sync indicates that the delete and cleanup should be done + // synchronously before returning to the caller + // + // Default is false + bool sync = 2; + + // Target value for image to be deleted + // + // If image descriptor does not match the same digest, + // the delete operation will return "not found" error. + optional containerd.types.Descriptor target = 3; +} diff --git a/userspace/libsinsp/examples/test.cpp b/userspace/libsinsp/examples/test.cpp index 679b53af22b..3d56d725228 100644 --- a/userspace/libsinsp/examples/test.cpp +++ b/userspace/libsinsp/examples/test.cpp @@ -470,8 +470,6 @@ int main(int argc, char** argv) { } } - inspector.set_cri_socket_path(""); - auto events_sc_codes = extract_filter_sc_codes(inspector); if(!events_sc_codes.empty()) { auto events_sc_names = libsinsp::events::sc_set_to_sc_names(events_sc_codes);