Skip to content

Commit

Permalink
cleanup: complete the /proc scan
Browse files Browse the repository at this point in the history
Signed-off-by: Andrea Terzolo <[email protected]>
  • Loading branch information
Andreagit97 authored and poiana committed Jul 24, 2024
1 parent 4585f3b commit 1c569c4
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 55 deletions.
25 changes: 15 additions & 10 deletions plugins/k8smeta/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,23 @@ plugins:
# path to the plugin .so file
library_path: libk8smeta.so
init_config:
# port exposed by the k8s-metacollector (required)
collectorPort: 45000
# hostname exposed by the k8s-metacollector (required)
collectorHostname: localhost
# name of the node on which the Falco instance is running. (required)
nodeName: kind-control-plane
# verbosity level for the plugin logger (optional)
verbosity: warning # (default: info)
# path to the PEM encoding of the server root certificates. (optional)
# port exposed by the k8s-metacollector
collectorPort: 45000 # (required)
# hostname exposed by the k8s-metacollector
collectorHostname: localhost # (required)
# name of the node on which the Falco instance is running.
nodeName: kind-control-plane # (required)
# verbosity level for the plugin logger
verbosity: warning # (optional, default: info)
# path to the PEM encoding of the server root certificates.
# Used to open an authanticated GRPC channel with the collector.
# If empty the connection will be insecure.
caPEMBundle: /etc/ssl/certs/ca-certificates.crt
caPEMBundle: /etc/ssl/certs/ca-certificates.crt # (optional)
# 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 '/'.
hostProc: /host # (optional, default: /host)

load_plugins: [k8smeta]
```
Expand Down
1 change: 1 addition & 0 deletions plugins/k8smeta/falco.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ plugins:
collectorHostname: localhost
nodeName: kind-control-plane
verbosity: critical
hostProc: /host

stdout_output:
enabled: true
2 changes: 1 addition & 1 deletion plugins/k8smeta/src/grpc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ K8sMetaClient::K8sMetaClient(const std::string& node_name,
sel.set_nodename(node_name);
sel.clear_resourcekinds();

/// todo! one day we could expose them to the user.
/// todo!: one day we could expose them to the user.
(*sel.mutable_resourcekinds())["Pod"] = "true";
(*sel.mutable_resourcekinds())["Namespace"] = "true";
(*sel.mutable_resourcekinds())["Deployment"] = "true";
Expand Down
119 changes: 94 additions & 25 deletions plugins/k8smeta/src/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ std::string get_pod_uid_from_cgroup_string(const std::string& cgroup_first_line)
// Example:
// `cpuset=/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-pod05869489-8c7f-45dc-9abd-1b1620787bb1.slice/cri-containerd-2f92446a3fbfd0b7a73457b45e96c75a25c5e44e7b1bcec165712b906551c261.scope\0`
//
// 2 - If it arrives from the /proc scan -> `hierarchy
// ID:controller:cgroup_path` Check if the cgroup version is relevant here
// or not...
// todo!: i'm not sure if all controllers have the same format in cgroupv1
// 2 - If it arrives from the /proc scan ->
// `hierarchyID:controller:cgroup_path`
// Example (cgroup v2):
// `0::/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod93f64796_43b9_468d_b77b_c652c985d5e0.slice`
// Example (cgroup v1):
// `12:perf_event:/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod93f64796_43b9_468d_b77b_c652c985d5e0.slice`
if(re2::RE2::PartialMatch(cgroup_first_line, pattern, &pod_uid))
{
// Here `pod_uid` could have 2 possible layouts:
Expand All @@ -90,7 +92,6 @@ std::string get_pod_uid_from_cgroup_string(const std::string& cgroup_first_line)

falcosecurity::init_schema my_plugin::get_init_schema()
{
/// todo!: check config names
falcosecurity::init_schema init_schema;
init_schema.schema_type =
falcosecurity::init_schema_type::SS_PLUGIN_SCHEMA_JSON;
Expand Down Expand Up @@ -134,6 +135,11 @@ falcosecurity::init_schema my_plugin::get_init_schema()
"type": "string",
"title": "The path to the PEM encoding of the server root certificates",
"description": "The path to the PEM encoding of the server root certificates. E.g. '/etc/ssl/certs/ca-certificates.crt'"
},
"hostProc": {
"type": "string",
"title": "Path to reach the '/proc' folder we want to scan.",
"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 '/'."
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -189,7 +195,7 @@ void my_plugin::parse_init_config(nlohmann::json& config_json)
config_json.at(nlohmann::json::json_pointer(NODENAME_PATH))
.get_to(nodename_string);

// todo!: remove it when we solved in Falco
// todo!: Solved in Falco 0.37.0 wait until Falco 0.36.2 is barely used
// This is just a simple workaround until we solve the Falco issue
// If the provided string is an env variable we use the content
// of the env variable
Expand Down Expand Up @@ -250,6 +256,17 @@ void my_plugin::parse_init_config(nlohmann::json& config_json)
}
}
}

if(config_json.contains(nlohmann::json::json_pointer(HOST_PROC_PATH)))
{
config_json.at(nlohmann::json::json_pointer(HOST_PROC_PATH))
.get_to(m_host_proc);
}
else
{
// Default value
m_host_proc = "/host";
}
}

void my_plugin::do_initial_proc_scan()
Expand All @@ -258,7 +275,7 @@ void my_plugin::do_initial_proc_scan()
std::string proc_root = m_host_proc + "/proc";
try
{
SPDLOG_DEBUG("Start the /proc scan under: '{}'", proc_root);
SPDLOG_INFO("Start the process scan under: '{}'", proc_root);
dir_iter = std::filesystem::directory_iterator(proc_root);
}
catch(std::filesystem::filesystem_error& err)
Expand Down Expand Up @@ -290,7 +307,6 @@ void my_plugin::do_initial_proc_scan()
.append("/")
.append(file_name.c_str())
.append("/cgroup");
SPDLOG_TRACE("Try to scan under: '{}'", proc_path);

std::ifstream file(proc_path);

Expand All @@ -299,26 +315,33 @@ void my_plugin::do_initial_proc_scan()
// Read the first line from the file
if(std::getline(file, cgroup_line))
{
// todo!: check the cgroupv1 layout
std::string pod_uid =
get_pod_uid_from_cgroup_string(cgroup_line);
if(!pod_uid.empty())
{
m_thread_id_pod_uid_map[tid] = pod_uid;
SPDLOG_TRACE("Found thread with tid '{}' and pod uid '{}'",
tid, pod_uid);
}
}
else
{
SPDLOG_WARN("cannot retrieve the cgroup first line for '{}'",
proc_path);
SPDLOG_WARN("cannot retrieve the cgroup first line for '{}'. "
"Error: {}. Skip it",
proc_path,
file.eof() ? "Empty file" : strerror(errno));
}
file.close();
}
else
{
SPDLOG_WARN("cannot open '{}'", proc_path);
SPDLOG_WARN("cannot open '{}'. Error: {}. Skip it.", proc_path,
strerror(errno));
}
}
SPDLOG_INFO(
"Process scan correctly completed. Found '{}' threads inside pods.",
m_thread_id_pod_uid_map.size());
}

bool my_plugin::init(falcosecurity::init_input& in)
Expand Down Expand Up @@ -653,7 +676,7 @@ bool inline my_plugin::extract_name_from_meta(
nlohmann::json& meta_json, falcosecurity::extract_request& req)
{
std::string resource_name;
// todo! Possible optimization here and in some other places, some paths
// todo!: Possible optimization here and in some other places, some paths
// should always be there.
if(!meta_json.contains(nlohmann::json::json_pointer(NAME_PATH)))
{
Expand Down Expand Up @@ -1042,15 +1065,22 @@ bool my_plugin::extract(const falcosecurity::extract_fields_input& in)
// The process is not into a pod, stop here.
if(pod_uid.empty())
{
// We try to obtain the pod_uid from our internal cache populated during
// the initial /proc scan
// If we fall here and our cache is empty, it means that probably we are
// not in a pod.
if(m_thread_id_pod_uid_map.empty())
{
return false;
}

// If the cache is not empty we try to search the pod_uid in the cache.
// There could be cases in which we first call an extract and then a
// parse so the sinsp table is not yet populated with the content of our
// cache and so we need to use it here.
auto it = m_thread_id_pod_uid_map.find(thread_id);
if(it == m_thread_id_pod_uid_map.end())
{
return false;
}
// The ideal thing would be to write it in the sinsp thread table but in
// the extraction phase we don't have a table writer.
pod_uid = it->second;
}

Expand Down Expand Up @@ -1336,7 +1366,7 @@ bool inline my_plugin::parse_process_events(
return false;
}

/// todo! Possible optimization, we can set the pod_uid only if we are in a
/// todo!: Possible optimization, we can set the pod_uid only if we are in a
/// container
// but we need to access the `m_flags` field to understand if we are in a
// container or not. It's also true that if we enable this plugin we are in
Expand All @@ -1359,14 +1389,19 @@ bool inline my_plugin::parse_process_events(
std::string cgroup_first_charbuf = (char*)cgroup_param.param_pointer;
std::string pod_uid = get_pod_uid_from_cgroup_string(cgroup_first_charbuf);

// retrieve thread entry associated with the event tid
auto& tr = in.get_table_reader();
auto thread_entry = m_thread_table.get_entry(
tr, (int64_t)in.get_event_reader().get_tid());
// If we don't have a pod_uid we don't need to populate the table
if(pod_uid != "")
{
// retrieve thread entry associated with the event tid
auto& tr = in.get_table_reader();
auto thread_entry = m_thread_table.get_entry(
tr, (int64_t)in.get_event_reader().get_tid());

// Write the pod_uid into the entry
auto& tw = in.get_table_writer();
m_pod_uid_field.write_value(tw, thread_entry, (const char*)pod_uid.c_str());
// Write the pod_uid into the entry
auto& tw = in.get_table_writer();
m_pod_uid_field.write_value(tw, thread_entry,
(const char*)pod_uid.c_str());
}
return true;
}

Expand All @@ -1375,6 +1410,40 @@ bool my_plugin::parse_event(const falcosecurity::parse_event_input& in)
// NOTE: today in the libs framework, parsing errors are not logged
auto& evt = in.get_event_reader();

// Workaround: the parsing is the unique place where we can populate the
// sinsp thread table. The first time we call parse we populate the sinsp
// table and we clear our internal cache.
if(!m_sinsp_proc_populated)
{
auto& tr = in.get_table_reader();
auto& tw = in.get_table_writer();
falcosecurity::table_entry thread_entry;

SPDLOG_INFO("Update the framework state with the plugin cache. The "
"cache has '{}' "
"elements",
m_thread_id_pod_uid_map.size());

for(auto it = m_thread_id_pod_uid_map.begin();
it != m_thread_id_pod_uid_map.end(); it++)
{
try
{
thread_entry = m_thread_table.get_entry(tr, (int64_t)it->first);
m_pod_uid_field.write_value(tw, thread_entry,
(const char*)it->second.c_str());
}
catch(falcosecurity::plugin_exception e)
{
SPDLOG_WARN("Thead id '{}' with pod_uid '{}' is not found "
"inside the framework table. Skip it.",
it->first, it->second);
}
}
m_thread_id_pod_uid_map.clear();
m_sinsp_proc_populated = true;
}

switch(evt.get_type())
{
case PPME_ASYNCEVENT_E:
Expand Down
4 changes: 3 additions & 1 deletion plugins/k8smeta/src/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ class my_plugin
std::string m_collector_port;
std::string m_node_name;
std::string m_ca_PEM_encoding;
// todo!: populate it when parsing the config.
std::string m_host_proc;

// State tables
Expand All @@ -268,6 +267,9 @@ class my_plugin
std::unordered_map<std::string, resource_layout> m_deamonset_table;
std::unordered_map<int64_t, std::string> m_thread_id_pod_uid_map;

// The first time we parse an event we populate the sinsp thread table and
// we set it to true
bool m_sinsp_proc_populated = false;
// Last error of the plugin
std::string m_lasterr;
// Accessor to the thread table
Expand Down
3 changes: 2 additions & 1 deletion plugins/k8smeta/src/shared_with_tests_consts.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ limitations under the License.
// Generic plugin consts
/////////////////////////
#define PLUGIN_NAME "k8smeta"
#define PLUGIN_VERSION "0.1.0"
#define PLUGIN_VERSION "0.1.1"
#define PLUGIN_DESCRIPTION \
"Enrich syscall events with information about the pod that throws them"
#define PLUGIN_CONTACT "github.com/falcosecurity/plugins"
Expand All @@ -99,3 +99,4 @@ limitations under the License.
#define PORT_PATH "/collectorPort"
#define NODENAME_PATH "/nodeName"
#define CA_CERT_PATH "/caPEMBundle"
#define HOST_PROC_PATH "/hostProc"
2 changes: 1 addition & 1 deletion plugins/k8smeta/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ To run only some tests you need to use the test binary directly

```bash
# from the `build` directory
sudo ./libs_tests/libsinsp/test/unit-test-libsinsp --gtest_filter='*plugin_k8s_PPME_SYSCALL_CLONE3_X_parse'
./libs_tests/libsinsp/test/unit-test-libsinsp --gtest_filter='*plugin_k8s_PPME_SYSCALL_CLONE3_X_parse'
```
2 changes: 1 addition & 1 deletion plugins/k8smeta/test/include/k8smeta_tests/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ limitations under the License.
#define INIT_CONFIG \
"{\"collectorHostname\":\"localhost\",\"collectorPort\": " \
"45000,\"nodeName\":\"control-plane\",\"verbosity\":" \
"\"info\"}"
"\"info\", \"hostProc\":\"\"}"

#define ASSERT_STRING_SETS(a, b) \
{ \
Expand Down
12 changes: 0 additions & 12 deletions plugins/k8smeta/test/src/check_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -674,15 +674,3 @@ TEST_F(sinsp_with_test_input, plugin_k8s_update_a_pod)
"10.16.1.20");
m_inspector.close();
}

////////////////////////////////////
// Missing tests
//////////////////////////////////

/// todo! Add some tests

// add a test on a resource without the `/labels` key.

// Check on a scap file

// Read a scap-file/huge json file and evaluate perf
12 changes: 12 additions & 0 deletions plugins/k8smeta/test/src/init_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,15 @@ TEST_F(sinsp_with_test_input, plugin_k8s_env_variable)
err));
ASSERT_EQ(err, "");
}

TEST_F(sinsp_with_test_input, plugin_k8s_with_host_proc)
{
auto plugin_owner = m_inspector.register_plugin(PLUGIN_PATH);
ASSERT_TRUE(plugin_owner.get());
std::string err;

ASSERT_NO_THROW(plugin_owner->init(R"(
{"collectorHostname":"localhost","collectorPort":45000,"nodeName":"kind-control-plane", "hostProc": "/host"})",
err));
ASSERT_EQ(err, "");
}
Loading

0 comments on commit 1c569c4

Please sign in to comment.