Skip to content

Commit f8c60d8

Browse files
committed
new(plugins/k8smeta): update k8smeta plugin to require plugin API version 3.9.0.
Implement the suggested output fields feature, by suggesting `k8smeta.pod.name` and `k8smeta.ns.name` as output fields. Also, entirely avoid the proc scan, instead relying on the listening CAPability to initially loop over the thread table to attach pod_uid to threads. `hostProc` initConfig key is now deprecated and unused. Moved the plugin to 0.3.0 too. Signed-off-by: Federico Di Pierro <[email protected]>
1 parent 467f7c7 commit f8c60d8

File tree

4 files changed

+81
-210
lines changed

4 files changed

+81
-210
lines changed

plugins/k8smeta/cmake/modules/plugin-sdk-cpp.cmake

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ message(
66
FetchContent_Declare(
77
plugin-sdk-cpp
88
GIT_REPOSITORY https://github.com/falcosecurity/plugin-sdk-cpp.git
9-
GIT_TAG 2097bdb5a5d77f3f38162da1f438382912465340)
9+
GIT_TAG 0.2.0)
1010

1111
FetchContent_MakeAvailable(plugin-sdk-cpp)
1212
set(PLUGIN_SDK_INLCUDE "${plugin-sdk-cpp_SOURCE_DIR}/include")

plugins/k8smeta/src/plugin.cpp

+65-199
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ falcosecurity::init_schema my_plugin::get_init_schema()
138138
},
139139
"hostProc": {
140140
"type": "string",
141-
"title": "Path to reach the '/proc' folder we want to scan.",
142-
"description": "The plugin needs to scan the '/proc' of the host on which is running. In Falco usually we put the host '/proc' folder under '/host/proc' so the the default for this config is '/host'. The path used here must not have a final '/'."
141+
"title": "[DEPRECATED] Path to reach the '/proc' folder we want to scan.",
142+
"description": "[DEPRECATED] The plugin needs to scan the '/proc' of the host on which is running. In Falco usually we put the host '/proc' folder under '/host/proc' so the the default for this config is '/host'. The path used here must not have a final '/'."
143143
}
144144
},
145145
"additionalProperties": false,
@@ -256,139 +256,6 @@ void my_plugin::parse_init_config(nlohmann::json& config_json)
256256
}
257257
}
258258
}
259-
260-
if(config_json.contains(nlohmann::json::json_pointer(HOST_PROC_PATH)))
261-
{
262-
config_json.at(nlohmann::json::json_pointer(HOST_PROC_PATH))
263-
.get_to(m_host_proc);
264-
}
265-
else
266-
{
267-
// Default value
268-
m_host_proc = "/host";
269-
}
270-
}
271-
272-
void my_plugin::do_initial_proc_scan()
273-
{
274-
std::filesystem::directory_iterator dir_iter;
275-
std::string proc_root = m_host_proc + "/proc";
276-
try
277-
{
278-
SPDLOG_INFO("Start the process scan under: '{}'", proc_root);
279-
dir_iter = std::filesystem::directory_iterator(proc_root);
280-
}
281-
catch(std::filesystem::filesystem_error& err)
282-
{
283-
SPDLOG_ERROR("cannot iter over '{}' for initial proc scan: {}",
284-
proc_root, err.what());
285-
return;
286-
}
287-
288-
int64_t tid = 0;
289-
std::string proc_path = "";
290-
std::string cgroup_line = "";
291-
for(const auto& entry : dir_iter)
292-
{
293-
auto file_name = entry.path().filename();
294-
// The file_name here should be `1`,`2` so the thread id, not the
295-
// process id. We should exclude other directories or files like
296-
// `bootconfig`,`vmstat`, ...
297-
298-
if(!entry.is_directory() ||
299-
(tid = strtol(file_name.c_str(), NULL, 10)) == 0)
300-
{
301-
// skip if not a tid directory.
302-
continue;
303-
}
304-
305-
// Now scan /proc/pid/task for threads
306-
std::string task_dir = proc_root + "/" + file_name.c_str() + "/task";
307-
308-
try
309-
{
310-
for(const auto& task_entry :
311-
std::filesystem::directory_iterator(task_dir))
312-
{
313-
try
314-
{
315-
auto task_file_name = task_entry.path().filename();
316-
tid = strtol(task_file_name.c_str(), NULL, 10);
317-
318-
if(tid == 0 || !task_entry.is_directory())
319-
{
320-
SPDLOG_WARN("Found task entry `{}` in process `{}` "
321-
"that is not "
322-
"a number",
323-
task_file_name.c_str(), file_name.c_str());
324-
continue; // skip if not a thread id directory
325-
}
326-
// Example of the path: `/proc/1/task/200/cgroup`
327-
proc_path = std::string(proc_root)
328-
.append("/")
329-
.append(file_name.c_str())
330-
.append("/task/")
331-
.append(task_file_name.c_str())
332-
.append("/cgroup");
333-
334-
std::ifstream file(proc_path);
335-
336-
if(file.is_open())
337-
{
338-
// Read the first line from the file
339-
if(std::getline(file, cgroup_line))
340-
{
341-
std::string pod_uid =
342-
get_pod_uid_from_cgroup_string(cgroup_line);
343-
if(!pod_uid.empty())
344-
{
345-
m_thread_id_pod_uid_map[tid] = pod_uid;
346-
SPDLOG_TRACE("Found thread with tid '{}' and "
347-
"pod uid '{}'",
348-
tid, pod_uid);
349-
}
350-
}
351-
else
352-
{
353-
SPDLOG_WARN("Cannot retrieve the cgroup first line "
354-
"for '{}'. "
355-
"Error: {}. Skip it",
356-
proc_path,
357-
file.eof() ? "Empty file"
358-
: strerror(errno));
359-
}
360-
file.close();
361-
}
362-
else
363-
{
364-
SPDLOG_WARN("Cannot open '{}'. Error: {}. Skip it.",
365-
proc_path, strerror(errno));
366-
}
367-
368-
SPDLOG_DEBUG(
369-
"Thread scan correctly completed for process `{}`",
370-
file_name.c_str());
371-
}
372-
catch(const std::filesystem::filesystem_error& err)
373-
{
374-
SPDLOG_WARN("cannot iter over '{}' for initial proc scan: "
375-
"{}. It could happen that some process "
376-
"entries disappear between iterations",
377-
task_dir, err.what());
378-
}
379-
}
380-
}
381-
catch(const std::filesystem::filesystem_error& err)
382-
{
383-
SPDLOG_WARN("cannot iter over '{}' for initial proc scan: {}. It "
384-
"could happen that some process entries disappear "
385-
"between iterations",
386-
task_dir, err.what());
387-
}
388-
}
389-
SPDLOG_INFO(
390-
"Process scan correctly completed. Found '{}' threads inside pods.",
391-
m_thread_id_pod_uid_map.size());
392259
}
393260

394261
bool my_plugin::init(falcosecurity::init_input& in)
@@ -428,11 +295,20 @@ bool my_plugin::init(falcosecurity::init_input& in)
428295
try
429296
{
430297
m_thread_table = t.get_table(THREAD_TABLE_NAME, st::SS_PLUGIN_ST_INT64);
298+
299+
// get the 'cgroups' field accessor from the thread table
300+
m_thread_field_cgroups = m_thread_table.get_field(
301+
t.fields(), CGROUPS_TABLE_NAME, st::SS_PLUGIN_ST_TABLE);
302+
// get the 'second' field accessor from the cgroups table
303+
m_cgroups_field_second = t.get_subtable_field(
304+
m_thread_table, m_thread_field_cgroups, "second",
305+
st::SS_PLUGIN_ST_STRING);
306+
431307
// Add the pod_uid field into thread table
432308
m_pod_uid_field = m_thread_table.add_field(
433309
t.fields(), POD_UID_FIELD_NAME, st::SS_PLUGIN_ST_STRING);
434310
}
435-
catch(falcosecurity::plugin_exception e)
311+
catch(falcosecurity::plugin_exception& e)
436312
{
437313
m_lasterr = "cannot add the '" + std::string(POD_UID_FIELD_NAME) +
438314
"' field into the '" + std::string(THREAD_TABLE_NAME) +
@@ -441,11 +317,53 @@ bool my_plugin::init(falcosecurity::init_input& in)
441317
return false;
442318
}
443319

444-
// Here we do /proc scan to catch the pod_uid from already running
445-
// processes.
446-
// We cannot populate the sinsp thread table because when we call `init` it
447-
// is still empty. The /proc scan in sinsp is done after the plugin init.
448-
do_initial_proc_scan();
320+
return true;
321+
}
322+
323+
//////////////////////////
324+
// Listen capability
325+
//////////////////////////
326+
327+
bool my_plugin::capture_open(const falcosecurity::capture_listen_input& in) {
328+
using st = falcosecurity::state_value_type;
329+
330+
SPDLOG_DEBUG("enriching initial thread table entries");
331+
auto& tr = in.get_table_reader();
332+
auto& tw = in.get_table_writer();
333+
m_thread_table.iterate_entries(
334+
tr,
335+
[this, tr, tw](const falcosecurity::table_entry& e)
336+
{
337+
try {
338+
auto cgroups_table = m_thread_table.get_subtable(tr,
339+
m_thread_field_cgroups, e, st::SS_PLUGIN_ST_UINT64);
340+
cgroups_table.iterate_entries(tr, [&](const falcosecurity::table_entry& e){
341+
// read the "second" field (aka: the cgroup path)
342+
// from the current entry of the cgroups table
343+
std::string cgroup;
344+
m_cgroups_field_second.read_value(tr, e, cgroup);
345+
if(!cgroup.empty()) {
346+
const std::string pod_uid = get_pod_uid_from_cgroup_string(cgroup);
347+
if(!pod_uid.empty())
348+
{
349+
m_pod_uid_field.write_value(tw, e, pod_uid.c_str());
350+
// break the loop
351+
return false;
352+
}
353+
}
354+
return true;
355+
});
356+
return true;
357+
} catch (falcosecurity::plugin_exception &e) {
358+
SPDLOG_ERROR("cannot attach pod_uid to process: {}", e.what());
359+
// break the loop
360+
return false;
361+
}
362+
});
363+
return true;
364+
}
365+
366+
bool my_plugin::capture_close(const falcosecurity::capture_listen_input& in) {
449367
return true;
450368
}
451369

@@ -520,7 +438,7 @@ std::vector<falcosecurity::field_info> my_plugin::get_fields()
520438
// Use an array to perform a static_assert one the size.
521439
const falcosecurity::field_info fields[] = {
522440
{ft::FTYPE_STRING, "k8smeta.pod.name", "Pod Name",
523-
"Kubernetes pod name."},
441+
"Kubernetes pod name.", {}, false, {}, true}, // use as suggested output format
524442
{ft::FTYPE_STRING, "k8smeta.pod.uid", "Pod UID",
525443
"Kubernetes pod UID."},
526444
{ft::FTYPE_STRING,
@@ -535,7 +453,7 @@ std::vector<falcosecurity::field_info> my_plugin::get_fields()
535453
{ft::FTYPE_STRING, "k8smeta.pod.ip", "Pod Ip", "Kubernetes pod ip"},
536454

537455
{ft::FTYPE_STRING, "k8smeta.ns.name", "Namespace Name",
538-
"Kubernetes namespace name."},
456+
"Kubernetes namespace name.", {}, false, {}, true}, // use as suggested output format
539457
{ft::FTYPE_STRING, "k8smeta.ns.uid", "Namespace UID",
540458
"Kubernetes namespace UID."},
541459
{ft::FTYPE_STRING,
@@ -1112,27 +1030,9 @@ bool my_plugin::extract(const falcosecurity::extract_fields_input& in)
11121030
// The process is not into a pod, stop here.
11131031
if(pod_uid.empty())
11141032
{
1115-
// If we fall here and our cache is empty, it means that probably we are
1116-
// not in a pod.
1117-
if(m_thread_id_pod_uid_map.empty())
1118-
{
1119-
SPDLOG_TRACE("no pod uid in the framework table for thread id '{}'",
1120-
thread_id);
1121-
return false;
1122-
}
1123-
1124-
// If the cache is not empty we try to search the pod_uid in the cache.
1125-
// There could be cases in which we first call an extract and then a
1126-
// parse so the sinsp table is not yet populated with the content of our
1127-
// cache and so we need to use it here.
1128-
auto it = m_thread_id_pod_uid_map.find(thread_id);
1129-
if(it == m_thread_id_pod_uid_map.end())
1130-
{
1131-
SPDLOG_TRACE("no pod uid in the plugin cache for thread id '{}'",
1132-
thread_id);
1133-
return false;
1134-
}
1135-
pod_uid = it->second;
1033+
SPDLOG_TRACE("no pod uid in the framework table for thread id '{}'",
1034+
thread_id);
1035+
return false;
11361036
}
11371037

11381038
// Try to find the entry associated with the pod_uid
@@ -1468,7 +1368,7 @@ bool inline my_plugin::parse_process_events(
14681368
std::string pod_uid = get_pod_uid_from_cgroup_string(cgroup_first_charbuf);
14691369

14701370
// If we don't have a pod_uid we don't need to populate the table
1471-
if(pod_uid != "")
1371+
if(!pod_uid.empty())
14721372
{
14731373
// retrieve thread entry associated with the event tid
14741374
auto& tr = in.get_table_reader();
@@ -1494,40 +1394,6 @@ bool my_plugin::parse_event(const falcosecurity::parse_event_input& in)
14941394
{
14951395
// NOTE: today in the libs framework, parsing errors are not logged
14961396

1497-
// Workaround: the parsing is the unique place where we can populate the
1498-
// sinsp thread table. The first time we call parse we populate the sinsp
1499-
// table and we clear our internal cache.
1500-
if(!m_sinsp_proc_populated)
1501-
{
1502-
auto& tr = in.get_table_reader();
1503-
auto& tw = in.get_table_writer();
1504-
falcosecurity::table_entry thread_entry;
1505-
1506-
SPDLOG_INFO("Update the framework state with the plugin cache. The "
1507-
"cache has '{}' "
1508-
"elements",
1509-
m_thread_id_pod_uid_map.size());
1510-
1511-
for(auto it = m_thread_id_pod_uid_map.begin();
1512-
it != m_thread_id_pod_uid_map.end(); it++)
1513-
{
1514-
try
1515-
{
1516-
thread_entry = m_thread_table.get_entry(tr, (int64_t)it->first);
1517-
m_pod_uid_field.write_value(tw, thread_entry,
1518-
(const char*)it->second.c_str());
1519-
}
1520-
catch(falcosecurity::plugin_exception e)
1521-
{
1522-
SPDLOG_WARN("Thead id '{}' with pod_uid '{}' is not found "
1523-
"inside the framework table. Skip it.",
1524-
it->first, it->second);
1525-
}
1526-
}
1527-
m_thread_id_pod_uid_map.clear();
1528-
m_sinsp_proc_populated = true;
1529-
}
1530-
15311397
uint16_t evt_type = in.get_event_reader().get_type();
15321398
switch(evt_type)
15331399
{

plugins/k8smeta/src/plugin.h

+12-7
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,12 @@ class my_plugin
126126

127127
bool init(falcosecurity::init_input& in);
128128

129-
void do_initial_proc_scan();
129+
//////////////////////////
130+
// Listen capability
131+
//////////////////////////
132+
133+
bool capture_open(const falcosecurity::capture_listen_input& in);
134+
bool capture_close(const falcosecurity::capture_listen_input& in);
130135

131136
//////////////////////////
132137
// Async capability
@@ -245,7 +250,7 @@ class my_plugin
245250
private:
246251
// Async thread
247252
std::thread m_async_thread;
248-
std::atomic<bool> m_async_thread_quit;
253+
std::atomic<bool> m_async_thread_quit = false;
249254
std::condition_variable m_cv;
250255
std::mutex m_mu;
251256

@@ -254,7 +259,6 @@ class my_plugin
254259
std::string m_collector_port;
255260
std::string m_node_name;
256261
std::string m_ca_PEM_encoding;
257-
std::string m_host_proc;
258262

259263
// State tables
260264
std::unordered_map<std::string, resource_layout> m_pod_table;
@@ -265,15 +269,15 @@ class my_plugin
265269
std::unordered_map<std::string, resource_layout>
266270
m_replication_controller_table;
267271
std::unordered_map<std::string, resource_layout> m_deamonset_table;
268-
std::unordered_map<int64_t, std::string> m_thread_id_pod_uid_map;
269272

270-
// The first time we parse an event we populate the sinsp thread table and
271-
// we set it to true
272-
bool m_sinsp_proc_populated = false;
273273
// Last error of the plugin
274274
std::string m_lasterr;
275275
// Accessor to the thread table
276276
falcosecurity::table m_thread_table;
277+
// Accessors to the thread table "cgroups" table
278+
falcosecurity::table_field m_thread_field_cgroups;
279+
// Accessors to the thread table "cgroups" "second" field, ie: the cgroups path
280+
falcosecurity::table_field m_cgroups_field_second;
277281
// Accessors to the fixed fields of the thread table
278282
falcosecurity::table_field m_pod_uid_field;
279283
};
@@ -282,3 +286,4 @@ FALCOSECURITY_PLUGIN(my_plugin);
282286
FALCOSECURITY_PLUGIN_FIELD_EXTRACTION(my_plugin);
283287
FALCOSECURITY_PLUGIN_ASYNC_EVENTS(my_plugin);
284288
FALCOSECURITY_PLUGIN_EVENT_PARSING(my_plugin);
289+
FALCOSECURITY_PLUGIN_CAPTURE_LISTENING(my_plugin);

0 commit comments

Comments
 (0)