Skip to content

Commit

Permalink
feat(libsinsp): add support for containerd interface
Browse files Browse the repository at this point in the history
Signed-off-by: Roberto Scolaro <[email protected]>
  • Loading branch information
therealbobo committed Dec 10, 2024
1 parent 230ddfb commit bc7d08d
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 5 deletions.
2 changes: 1 addition & 1 deletion test/libsinsp_e2e/container/container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ TEST_F(sys_call_test, container_docker_bad_socket) {
ASSERT_NE(PPME_CONTAINER_JSON_2_E, param.m_evt->get_type());

sinsp_threadinfo* tinfo = param.m_evt->get_thread_info(false);
ASSERT_TRUE(tinfo->m_container_id.length() == 12);
ASSERT_TRUE(tinfo->m_container_id.length() <= 12);
ASSERT_TRUE(param.m_inspector->m_container_manager.container_exists(tinfo->m_container_id));
const auto container_info =
param.m_inspector->m_container_manager.get_container(tinfo->m_container_id);
Expand Down
50 changes: 50 additions & 0 deletions userspace/libsinsp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ if(NOT MINIMAL_BUILD AND NOT EMSCRIPTEN)
PRIVATE container_engine/docker/docker_linux.cpp
container_engine/docker/connection_linux.cpp
container_engine/docker/podman.cpp
container_engine/containerd.cpp
container_engine/libvirt_lxc.cpp
container_engine/lxc.cpp
container_engine/mesos.cpp
Expand Down Expand Up @@ -243,6 +244,53 @@ function(prepare_cri_grpc api_version)
endif()
endfunction()

function(prepare_containerd_grpc)
set(DEST ${CMAKE_CURRENT_BINARY_DIR}/container_engine/containerd)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/container_engine/containerd/containers.proto
${DEST}/containers.proto COPYONLY
)
add_custom_command(
OUTPUT ${DEST}/containers.grpc.pb.cc ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.cc
${DEST}/containers.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
WORKING_DIRECTORY ${DEST}
)
add_library(containerd_interface STATIC ${DEST}/containers.pb.cc ${DEST}/containers.grpc.pb.cc)
target_include_directories(containerd_interface PUBLIC $<BUILD_INTERFACE:${DEST}>)
target_link_libraries(
containerd_interface
PUBLIC "${GRPCPP_LIB}"
"${GRPC_LIB}"
"${GPR_LIB}"
"${GRPC_LIBRARIES}"
"${PROTOBUF_LIB}"
"${CARES_LIB}"
"${OPENSSL_LIBRARIES}"
)
add_dependencies(containerd_interface grpc)
install(
FILES ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.h
DESTINATION
"${CMAKE_INSTALL_INCLUDEDIR}/${LIBS_PACKAGE_NAME}/libsinsp/container_engine/containerd"
COMPONENT "scap"
)
if(NOT BUILD_SHARED_LIBS)
install(
TARGETS containerd_interface
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
COMPONENT "scap"
OPTIONAL
)
endif()
endfunction()

if(NOT EMSCRIPTEN)
add_dependencies(sinsp tbb)
endif()
Expand All @@ -260,8 +308,10 @@ if(NOT WIN32)
include(cares)
prepare_cri_grpc(v1alpha2)
prepare_cri_grpc(v1)
prepare_containerd_grpc()

target_link_libraries(sinsp PUBLIC cri_v1alpha2 cri_v1)
target_link_libraries(sinsp PUBLIC containerd_interface)

if(NOT MUSL_OPTIMIZED_BUILD)
find_library(LIB_ANL anl)
Expand Down
6 changes: 6 additions & 0 deletions userspace/libsinsp/container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ limitations under the License.
#include <libsinsp/container_engine/lxc.h>
#include <libsinsp/container_engine/mesos.h>
#include <libsinsp/container_engine/bpm.h>
#include <libsinsp/container_engine/containerd.h>
#endif // MINIMAL_BUILD
#include <libsinsp/container_engine/static_container.h>

Expand Down Expand Up @@ -599,6 +600,11 @@ void sinsp_container_manager::create_engines() {
m_container_engines.push_back(bpm_engine);
m_container_engine_by_type[CT_BPM] = bpm_engine;
}
if(m_container_engine_mask & (1 << CT_CONTAINERD)) {
auto containerd_engine = std::make_shared<container_engine::containerd>(*this);
m_container_engines.push_back(containerd_engine);
// m_container_engine_by_type[CT_CONTAINERD] = containerd_engine;
}
#endif // _WIN32
#endif // MINIMAL_BUILD
}
Expand Down
231 changes: 231 additions & 0 deletions userspace/libsinsp/container_engine/containerd.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <sys/stat.h>

#include <libsinsp/container_engine/containerd.h>
#include <libsinsp/cri.h>
#include <libsinsp/grpc_channel_registry.h>
#include <libsinsp/runc.h>
#include <libsinsp/sinsp.h>

using namespace libsinsp::container_engine;
using namespace libsinsp::runc;

constexpr const cgroup_layout CONTAINERD_CGROUP_LAYOUT[] = {{"/default/", ""}, {nullptr, nullptr}};

constexpr const std::string_view CONTAINERD_SOCKETS[] = {
"/run/host-containerd/containerd.sock", // bottlerocket host containers socket
"/run/containerd/runtime2/containerd.sock", // tmp
};

bool containerd_interface::is_ok() {

Check warning on line 37 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L37

Added line #L37 was not covered by tests
return m_stub != nullptr;
}
containerd_interface::containerd_interface(const std::string &socket_path) {
grpc::ChannelArguments args;
args.SetInt(GRPC_ARG_ENABLE_HTTP_PROXY, 0);
std::shared_ptr<grpc::Channel> channel =

Check warning on line 43 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L43

Added line #L43 was not covered by tests
libsinsp::grpc_channel_registry::get_channel("unix://" + socket_path, &args);

m_stub = ContainerdService::Containers::NewStub(channel);

ContainerdService::ListContainersRequest req;
ContainerdService::ListContainersResponse resp;

grpc::ClientContext context;
auto deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());

Check warning on line 53 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L53

Added line #L53 was not covered by tests
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");
grpc::Status status = m_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;

Check warning on line 69 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L69

Added line #L69 was not covered by tests
}
}

grpc::Status containerd_interface::list_container_resp(

Check warning on line 73 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L73

Added line #L73 was not covered by tests
const std::string &container_id,
ContainerdService::ListContainersResponse &resp) {
ContainerdService::ListContainersRequest req;

Check warning on line 76 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L76

Added line #L76 was not covered by tests

// To match the container using a truncated containerd id
// we need to use a match filter (~=).
req.add_filters("id~=" + container_id);
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());

Check warning on line 84 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L84

Added line #L84 was not covered by tests
context.set_deadline(deadline);
return m_stub->List(&context, req, &resp);
}

libsinsp::container_engine::containerd::containerd(container_cache_interface &cache):
container_engine_base(cache) {
for(const auto &p : CONTAINERD_SOCKETS) {
if(p.empty()) {
continue;

Check warning on line 93 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L93

Added line #L93 was not covered by tests
}

auto socket_path = scap_get_host_root() + std::string(p);
struct stat s = {};
if(stat(socket_path.c_str(), &s) != 0 || (s.st_mode & S_IFMT) != S_IFSOCK) {
continue;
}

m_interface = std::make_unique<containerd_interface>(socket_path);
if(!m_interface->is_ok()) {
m_interface.reset(nullptr);
continue;

Check warning on line 105 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L104-L105

Added lines #L104 - L105 were not covered by tests
}
}
}

bool libsinsp::container_engine::containerd::parse_containerd(sinsp_container_info &container,

Check warning on line 110 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L110

Added line #L110 was not covered by tests
const std::string &container_id) {
// given the truncated container id, the full container id needs to be retrivied from
// containerd.
ContainerdService::ListContainersResponse resp;

Check warning on line 114 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L114

Added line #L114 was not covered by tests
grpc::Status status = m_interface->list_container_resp(container_id, resp);

if(!status.ok()) {
libsinsp_logger()->format(
sinsp_logger::SEV_DEBUG,
"containerd (%s): ListContainerResponse status error message: (%s)",
container.m_id.c_str(),
status.error_message().c_str());
return false;

Check warning on line 123 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L123

Added line #L123 was not covered by tests
}

auto containers = resp.containers();

if(containers.size() == 0) {
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
"containerd (%s): ListContainerResponse status error message: "
"(container id has no match)",
container.m_id.c_str());
return false;
} else if(containers.size() > 1) {
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
"containerd (%s): ListContainerResponse status error message: "
"(container id has more than one match)",
container.m_id.c_str());
return false;
}

// Usually the image has this form: `docker.io/library/ubuntu:22.04`
auto raw_image_splits = sinsp_split(containers[0].image(), ':');

container.m_id = container_id;
container.m_full_id = containers[0].id();
// We assume that the last `/`-separated field is the image
container.m_image = raw_image_splits[0].substr(raw_image_splits[0].rfind("/") + 1);
// 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;

Check warning on line 153 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L153

Added line #L153 was not covered by tests

// Retrieve the labels.
for(const auto &pair : containers[0].labels()) {
if(pair.second.length() <= sinsp_container_info::m_container_label_max_length) {
container.m_labels[pair.first] = pair.second;
}
}

// The spec field keeps the information about the mounts.
Json::Value spec;
Json::Reader reader;
// The spec field of the response is just a raw json.
reader.parse(containers[0].spec().value(), spec);

// Retrieve the mounts.
for(const auto &m : spec["mounts"]) {
bool readonly = false;

Check warning on line 170 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L170

Added line #L170 was not covered by tests
std::string mode;
for(const auto &jopt : m["options"]) {
std::string opt = jopt.asString();
if(opt == "ro") {
readonly = true;
} else if(opt.rfind("mode=") == 0) {
mode = opt.substr(5);
}
}
container.m_mounts.emplace_back(m["source"].asString(),
m["destination"].asString(),
mode,
!readonly,
spec["linux"]["rootfsPropagation"].asString());
}

// Retrieve the env.
for(const auto &env : spec["process"]["env"]) {
container.m_env.emplace_back(env.asString());
}

return true;

Check warning on line 192 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L192

Added line #L192 was not covered by tests
}

bool libsinsp::container_engine::containerd::resolve(sinsp_threadinfo *tinfo,
bool query_os_for_missing_info) {
auto container = sinsp_container_info();
std::string container_id, cgroup;

if(!matches_runc_cgroups(tinfo, CONTAINERD_CGROUP_LAYOUT, container_id, cgroup)) {
return false;
}

if(!parse_containerd(container, container_id)) {
return false;
}

tinfo->m_container_id = container_id;

libsinsp::cgroup_limits::cgroup_limits_key key(container.m_id,

Check warning on line 210 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L210

Added line #L210 was not covered by tests
tinfo->get_cgroup("cpu"),
tinfo->get_cgroup("memory"),
tinfo->get_cgroup("cpuset"));

libsinsp::cgroup_limits::cgroup_limits_value limits;
libsinsp::cgroup_limits::get_cgroup_resource_limits(key, limits);

container.m_memory_limit = limits.m_memory_limit;
container.m_cpu_shares = limits.m_cpu_shares;
container.m_cpu_quota = limits.m_cpu_quota;
container.m_cpu_period = limits.m_cpu_period;
container.m_cpuset_cpu_count = limits.m_cpuset_cpu_count;

Check warning on line 222 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L218-L222

Added lines #L218 - L222 were not covered by tests

if(container_cache().should_lookup(container.m_id, CT_CONTAINERD)) {
container.m_name = container.m_id;
container.set_lookup_status(sinsp_container_lookup::state::SUCCESSFUL);
container_cache().add_container(std::make_shared<sinsp_container_info>(container), tinfo);
container_cache().notify_new_container(container, tinfo);
}
return true;

Check warning on line 230 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L230

Added line #L230 was not covered by tests
}
58 changes: 58 additions & 0 deletions userspace/libsinsp/container_engine/containerd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#pragma once

class sinsp_container_info;
class sinsp_threadinfo;

#include <libsinsp/container_engine/containerd/containers.grpc.pb.h>
#include <libsinsp/container_engine/container_engine_base.h>
#include <libsinsp/container_engine/sinsp_container_type.h>

namespace ContainerdService = containerd::services::containers::v1;

namespace libsinsp {
namespace container_engine {

class containerd_interface {
public:
containerd_interface(const std::string &socket_path);

grpc::Status list_container_resp(const std::string &container_id,
ContainerdService::ListContainersResponse &resp);

bool is_ok();

private:
std::unique_ptr<ContainerdService::Containers::Stub> m_stub;
};

class containerd : public container_engine_base {
public:
containerd(container_cache_interface &cache);

bool parse_containerd(sinsp_container_info &container, const std::string &container_id);
bool resolve(sinsp_threadinfo *tinfo, bool query_os_for_missing_info) override;

private:
std::unique_ptr<containerd_interface> m_interface;
};

} // namespace container_engine
} // namespace libsinsp
Loading

0 comments on commit bc7d08d

Please sign in to comment.