|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +/* |
| 3 | +Copyright (C) 2024 The Falco Authors. |
| 4 | +
|
| 5 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +you may not use this file except in compliance with the License. |
| 7 | +You may obtain a copy of the License at |
| 8 | +
|
| 9 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +
|
| 11 | +Unless required by applicable law or agreed to in writing, software |
| 12 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +See the License for the specific language governing permissions and |
| 15 | +limitations under the License. |
| 16 | +
|
| 17 | +*/ |
| 18 | + |
| 19 | +#include <sys/stat.h> |
| 20 | + |
| 21 | +#include <libsinsp/container_engine/containerd.h> |
| 22 | +#include <libsinsp/cri.h> |
| 23 | +#include <libsinsp/grpc_channel_registry.h> |
| 24 | +#include <libsinsp/runc.h> |
| 25 | +#include <libsinsp/sinsp.h> |
| 26 | + |
| 27 | +using namespace libsinsp::container_engine; |
| 28 | +using namespace libsinsp::runc; |
| 29 | + |
| 30 | +constexpr const cgroup_layout CONTAINERD_CGROUP_LAYOUT[] = {{"/default/", ""}, {nullptr, nullptr}}; |
| 31 | + |
| 32 | +constexpr const std::string_view CONTAINERD_SOCKETS[] = { |
| 33 | + "/run/host-containerd/containerd.sock", // bottlerocket host containers socket |
| 34 | + "/run/containerd/runtime2/containerd.sock", // tmp |
| 35 | +}; |
| 36 | + |
| 37 | +bool containerd_interface::is_ok() { |
| 38 | + return m_stub != nullptr; |
| 39 | +} |
| 40 | +containerd_interface::containerd_interface(const std::string &socket_path) { |
| 41 | + grpc::ChannelArguments args; |
| 42 | + args.SetInt(GRPC_ARG_ENABLE_HTTP_PROXY, 0); |
| 43 | + std::shared_ptr<grpc::Channel> channel = |
| 44 | + libsinsp::grpc_channel_registry::get_channel("unix://" + socket_path, &args); |
| 45 | + |
| 46 | + m_stub = ContainerdService::Containers::NewStub(channel); |
| 47 | + |
| 48 | + ContainerdService::ListContainersRequest req; |
| 49 | + ContainerdService::ListContainersResponse resp; |
| 50 | + |
| 51 | + grpc::ClientContext context; |
| 52 | + auto deadline = std::chrono::system_clock::now() + |
| 53 | + std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); |
| 54 | + context.set_deadline(deadline); |
| 55 | + context.AddMetadata("containerd-namespace", "default"); |
| 56 | + grpc::Status status = m_stub->List(&context, req, &resp); |
| 57 | + |
| 58 | + if(!status.ok()) { |
| 59 | + libsinsp_logger()->format(sinsp_logger::SEV_NOTICE, |
| 60 | + "containerd (%s): containerd runtime returned an error after " |
| 61 | + "trying to list containerd: %s", |
| 62 | + socket_path.c_str(), |
| 63 | + status.error_message().c_str()); |
| 64 | + m_stub.reset(nullptr); |
| 65 | + return; |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +grpc::Status containerd_interface::list_container_resp( |
| 70 | + const std::string &container_id, |
| 71 | + ContainerdService::ListContainersResponse &resp) { |
| 72 | + ContainerdService::ListContainersRequest req; |
| 73 | + |
| 74 | + std::string filter("id~="); |
| 75 | + // REPORTED_CONTAINERD_ID_LENGTH = 12 |
| 76 | + filter.reserve(16); |
| 77 | + filter.append(container_id); |
| 78 | + |
| 79 | + req.add_filters(filter); |
| 80 | + grpc::ClientContext context; |
| 81 | + context.AddMetadata("containerd-namespace", "default"); |
| 82 | + auto deadline = std::chrono::system_clock::now() + |
| 83 | + std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); |
| 84 | + context.set_deadline(deadline); |
| 85 | + return m_stub->List(&context, req, &resp); |
| 86 | +} |
| 87 | + |
| 88 | +grpc::Status containerd_interface::get_container_resp( |
| 89 | + const std::string &container_id, |
| 90 | + ContainerdService::GetContainerResponse &resp) { |
| 91 | + ContainerdService::GetContainerRequest req; |
| 92 | + req.set_id(container_id); |
| 93 | + grpc::ClientContext context; |
| 94 | + auto deadline = std::chrono::system_clock::now() + |
| 95 | + std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout()); |
| 96 | + context.set_deadline(deadline); |
| 97 | + return m_stub->Get(&context, req, &resp); |
| 98 | +} |
| 99 | + |
| 100 | +libsinsp::container_engine::containerd::containerd(container_cache_interface &cache): |
| 101 | + container_engine_base(cache) { |
| 102 | + for(const auto &p : CONTAINERD_SOCKETS) { |
| 103 | + if(p.empty()) { |
| 104 | + continue; |
| 105 | + } |
| 106 | + |
| 107 | + auto socket_path = scap_get_host_root() + std::string(p); |
| 108 | + struct stat s = {}; |
| 109 | + if(stat(socket_path.c_str(), &s) != 0 || (s.st_mode & S_IFMT) != S_IFSOCK) { |
| 110 | + continue; |
| 111 | + } |
| 112 | + |
| 113 | + m_interface = std::make_unique<containerd_interface>(socket_path); |
| 114 | + if(!m_interface->is_ok()) { |
| 115 | + m_interface.reset(nullptr); |
| 116 | + continue; |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +bool libsinsp::container_engine::containerd::parse_containerd(sinsp_container_info &container, |
| 122 | + const std::string &container_id) { |
| 123 | + // given the truncated container id, the full container id needs to be retrivied from |
| 124 | + // containerd. |
| 125 | + ContainerdService::ListContainersResponse resp; |
| 126 | + grpc::Status status = m_interface->list_container_resp(container_id, resp); |
| 127 | + |
| 128 | + if(!status.ok()) { |
| 129 | + libsinsp_logger()->format( |
| 130 | + sinsp_logger::SEV_DEBUG, |
| 131 | + "containerd (%s): ListContainerResponse status error message: (%s)", |
| 132 | + container.m_id.c_str(), |
| 133 | + status.error_message().c_str()); |
| 134 | + return false; |
| 135 | + } |
| 136 | + |
| 137 | + auto containers = resp.containers(); |
| 138 | + |
| 139 | + if(containers.size() == 0) { |
| 140 | + libsinsp_logger()->format(sinsp_logger::SEV_DEBUG, |
| 141 | + "containerd (%s): ListContainerResponse status error message: " |
| 142 | + "(container id has no match)", |
| 143 | + container.m_id.c_str()); |
| 144 | + return false; |
| 145 | + } else if(containers.size() > 1) { |
| 146 | + libsinsp_logger()->format(sinsp_logger::SEV_DEBUG, |
| 147 | + "containerd (%s): ListContainerResponse status error message: " |
| 148 | + "(container id has more than one match)", |
| 149 | + container.m_id.c_str()); |
| 150 | + return false; |
| 151 | + } |
| 152 | + |
| 153 | + auto raw_image_splits = sinsp_split(containers[0].image(), ':'); |
| 154 | + |
| 155 | + container.m_id = container_id; |
| 156 | + container.m_full_id = containers[0].id(); |
| 157 | + container.m_imagerepo = raw_image_splits[0].substr(0, raw_image_splits[0].rfind("/")); |
| 158 | + container.m_imagetag = raw_image_splits[1]; |
| 159 | + container.m_image = raw_image_splits[0].substr(raw_image_splits[0].rfind("/") + 1); |
| 160 | + container.m_imagedigest = ""; |
| 161 | + container.m_type = CT_CONTAINERD; |
| 162 | + |
| 163 | + for(const auto &pair : containers[0].labels()) { |
| 164 | + if(pair.second.length() <= sinsp_container_info::m_container_label_max_length) { |
| 165 | + container.m_labels[pair.first] = pair.second; |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + Json::Value spec; |
| 170 | + Json::Reader reader; |
| 171 | + // The spec field of the response is just a raw json. |
| 172 | + reader.parse(containers[0].spec().value(), spec); |
| 173 | + |
| 174 | + for(const auto &m : spec["mounts"]) { |
| 175 | + bool readonly = false; |
| 176 | + std::string mode; |
| 177 | + for(const auto &jopt : m["options"]) { |
| 178 | + std::string opt = jopt.asString(); |
| 179 | + if(opt == "ro") { |
| 180 | + readonly = true; |
| 181 | + } else if(opt.rfind("mode=") == 0) { |
| 182 | + mode = opt.substr(5); |
| 183 | + } |
| 184 | + } |
| 185 | + container.m_mounts.emplace_back(m["source"].asString(), |
| 186 | + m["destination"].asString(), |
| 187 | + mode, |
| 188 | + !readonly, |
| 189 | + spec["linux"]["rootfsPropagation"].asString()); |
| 190 | + } |
| 191 | + |
| 192 | + for(const auto &env : spec["process"]["env"]) { |
| 193 | + container.m_env.emplace_back(env.asString()); |
| 194 | + } |
| 195 | + |
| 196 | + return true; |
| 197 | +} |
| 198 | + |
| 199 | +bool libsinsp::container_engine::containerd::resolve(sinsp_threadinfo *tinfo, |
| 200 | + bool query_os_for_missing_info) { |
| 201 | + auto container = sinsp_container_info(); |
| 202 | + std::string container_id, cgroup; |
| 203 | + |
| 204 | + if(!matches_runc_cgroups(tinfo, CONTAINERD_CGROUP_LAYOUT, container_id, cgroup)) { |
| 205 | + return false; |
| 206 | + } |
| 207 | + |
| 208 | + if(!parse_containerd(container, container_id)) { |
| 209 | + return false; |
| 210 | + } |
| 211 | + |
| 212 | + libsinsp::cgroup_limits::cgroup_limits_key key(container.m_id, |
| 213 | + tinfo->get_cgroup("cpu"), |
| 214 | + tinfo->get_cgroup("memory"), |
| 215 | + tinfo->get_cgroup("cpuset")); |
| 216 | + |
| 217 | + libsinsp::cgroup_limits::cgroup_limits_value limits; |
| 218 | + libsinsp::cgroup_limits::get_cgroup_resource_limits(key, limits); |
| 219 | + |
| 220 | + container.m_memory_limit = limits.m_memory_limit; |
| 221 | + container.m_cpu_shares = limits.m_cpu_shares; |
| 222 | + container.m_cpu_quota = limits.m_cpu_quota; |
| 223 | + container.m_cpu_period = limits.m_cpu_period; |
| 224 | + container.m_cpuset_cpu_count = limits.m_cpuset_cpu_count; |
| 225 | + |
| 226 | + if(container_cache().should_lookup(container.m_id, CT_CONTAINERD)) { |
| 227 | + container.m_name = container.m_id; |
| 228 | + container.set_lookup_status(sinsp_container_lookup::state::SUCCESSFUL); |
| 229 | + container_cache().add_container(std::make_shared<sinsp_container_info>(container), tinfo); |
| 230 | + container_cache().notify_new_container(container, tinfo); |
| 231 | + } |
| 232 | + return true; |
| 233 | +} |
0 commit comments