Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update(k8smeta)!: improve logs and rename some fields #389

Merged
merged 6 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions plugins/k8smeta/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
# Kubernetes Audit Events Plugin
# Kubernetes metadata enrichment Plugin

## Experimental

Consider this plugin as experimental until it reaches version `1.0.0`. By 'experimental' we mean that, although the plugin is functional and tested, it is currently in active development and may undergo changes in behavior as necessary, without prioritizing backward compatibility.

## Introduction

This plugin enriches Falco syscall flow with Kubernetes Metadata coming from the API server.
The plugin uses a GRPC channel to communicate with a remote [collector](https://github.com/falcosecurity/k8s-metacollector). The collector is indipendent from the plugin and should be deployed as a separate component. The main role of the plugin is to associate each syscall with information about the pod in which they are thrown.
The `k8smeta` plugin enhances the Falco syscall source by providing additional information about the Kubernetes resources involved. For instance, when a syscall is thrown within a pod, it allows retrieving details about the pod, such as `uid`, `name`, `labels`, and more. It also provides information about resources associated with the pod like `deployments`, `services`, `replica-sets`, and others. You can find the comprehensive list of supported fields [here](#supported-fields).

### Functionality

TODO
The plugin gathers details about Kubernetes resources from a remote collector known as [`k8s-metacollector`](https://github.com/falcosecurity/k8s-metacollector). It then stores this information in tables and provides access to Falco upon request. The plugin specifically acquires data for the node where the associated Falco instance is deployed, resulting in node-level granularity. In contrast, the collector runs at the cluster level. This implies that within a given cluster, there may be multiple `k8smeta` plugins (one per node), but there is only one collector.

## Capabilities

The `k8smeta` plugin implements these capabilities:
The `k8smeta` plugin implements 3 capabilities:

* `extraction`
* `parsing`
* `async`

### Plugin official name

`k8smeta`

### Supported Fields

<!-- README-PLUGIN-FIELDS -->
Expand Down Expand Up @@ -55,30 +63,53 @@ The `k8smeta` plugin implements these capabilities:
Here's an example of configuration of `falco.yaml`:

```yaml
load_plugins: [k8smeta]

plugins:
- name: k8smeta
# 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
nodename: kind-control-plane
# name of the node on which the Falco instance is running. (required)
nodeName: kind-control-plane
# verbosity level for the plugin logger (optional)
verbosity: warn # (default: info)
# path to the PEM encoding of the server root certificates. (optional)
# Used to open an authanticated GRPC channel with the collector.
# If empty the connection will be insecure.
caPEMBundle: /etc/ssl/certs/ca-certificates.crt

load_plugins: [k8smeta]
```

**Initialization Config**:

TODO
See the [configuration](#configuration) section above.

**Open Parameters**:

The plugin doesn't have open params

### Rule Example
### Rules

This plugin doesn't provide any custom rule, you can use the default Falco ruleset and add the necessary `k8smeta` fields. A very simple example rule can be found [here](https://github.com/falcosecurity/plugins/blob/master/plugins/k8smeta/test/rules/example_rule.yaml)

### Running

This plugin requires Falco with version >= **0.37.0**.
Modify the `falco.yaml` with the [configuration above](#configuration) and you are ready to go!

To see how to use the plugin fields in a Falco rule check the example rule `/k8smeta/test/rules/example_rule.yaml`.
```shell
falco -c falco.yaml -r falco_rules.yaml
```

## Local development

### Build the plugin on a fresh Ubuntu 22.04 machine
### Build and test

Build the plugin on a fresh `Ubuntu 22.04` machine:

```bash
sudo apt update -y
Expand All @@ -89,3 +120,5 @@ mkdir build && cd build
cmake ..
make k8smeta -j10
```

To run local tests follow the steps [here](https://github.com/falcosecurity/plugins/blob/master/plugins/k8smeta/test/README.md)
2 changes: 1 addition & 1 deletion plugins/k8smeta/cmake/modules/libs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ message(STATUS "Fetching libs at 'https://github.com/falcosecurity/libs.git'")
FetchContent_Declare(
libs
GIT_REPOSITORY https://github.com/falcosecurity/libs.git
GIT_TAG 8fee2fb4791d50ec5ee4808e5ed235c8b1b309f3
GIT_TAG 0.14.0
CONFIGURE_COMMAND "" BUILD_COMMAND "")

FetchContent_Populate(libs)
Expand Down
2 changes: 1 addition & 1 deletion plugins/k8smeta/falco.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins:
init_config:
collectorPort: 45000
collectorHostname: localhost
nodename: kind-control-plane
nodeName: kind-control-plane
verbosity: critical

stdout_output:
Expand Down
6 changes: 3 additions & 3 deletions plugins/k8smeta/src/grpc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ using grpc::Status;
using metadata::Event;
using metadata::Selector;

K8sMetaClient::K8sMetaClient(const std::string& nodename,
K8sMetaClient::K8sMetaClient(const std::string& node_name,
const std::string& ip_port,
const std::string& ca_PEM_encoding, std::mutex& mu,
std::condition_variable& cv,
Expand All @@ -42,7 +42,7 @@ K8sMetaClient::K8sMetaClient(const std::string& nodename,
m_handler(handler), m_correctly_reading(0)
{
metadata::Selector sel;
sel.set_nodename(nodename);
sel.set_nodename(node_name);
sel.clear_resourcekinds();

/// todo! one day we could expose them to the user.
Expand Down Expand Up @@ -133,7 +133,7 @@ void K8sMetaClient::OnReadDone(bool ok)
// routines:OPENSSL_internal:WRONG_VERSION_NUMBER)
// ```
//
// 3. If the port or the nodename in the plugin init params are wrong
// 3. If the port or the node name in the plugin init params are wrong
// ```
// [2023-11-29 17:01:08.802] [error] [k8smeta] error during the RPC call. Error
// code (14), error message (failed to connect to all addresses; last error:
Expand Down
2 changes: 1 addition & 1 deletion plugins/k8smeta/src/grpc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ limitations under the License.
class K8sMetaClient : public grpc::ClientReadReactor<metadata::Event>
{
public:
K8sMetaClient(const std::string& nodename, const std::string& ip_port,
K8sMetaClient(const std::string& node_name, const std::string& ip_port,
const std::string& ca_PEM_encoding, std::mutex& mu,
std::condition_variable& cv, std::atomic<bool>& thread_quit,
falcosecurity::async_event_handler& handler);
Expand Down
25 changes: 15 additions & 10 deletions plugins/k8smeta/src/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ falcosecurity::init_schema my_plugin::get_init_schema()
"required": [
"collectorHostname",
"collectorPort",
"nodename"
"nodeName"
],
"properties": {
"verbosity": {
Expand All @@ -92,10 +92,10 @@ falcosecurity::init_schema my_plugin::get_init_schema()
"title": "The collector port",
"description": "The port used by the plugin to contact the collector (e.g. '45000')."
},
"nodename": {
"nodeName": {
"type": "string",
"title": "The node on which Falco is deployed",
"description": "The plugin collects k8s metadata only for the node on which Falco is deployed so the nodename must be specified."
"description": "The plugin collects k8s metadata only for the node on which Falco is deployed so the node name must be specified."
},
"caPEMBundle": {
"type": "string",
Expand Down Expand Up @@ -149,7 +149,7 @@ void my_plugin::parse_init_config(nlohmann::json& config_json)
assert(false);
}

// Nodename
// Node name
if(config_json.contains(nlohmann::json::json_pointer(NODENAME_PATH)))
{
std::string nodename_string = "";
Expand All @@ -169,20 +169,20 @@ void my_plugin::parse_init_config(nlohmann::json& config_json)
auto env_var_name = env_var.substr(2, env_var.length() - 3);
if(getenv(env_var_name.c_str()))
{
m_nodename = getenv(env_var_name.c_str());
m_node_name = getenv(env_var_name.c_str());
}
else
{
SPDLOG_CRITICAL("The provided env variable '{}' is empty",
env_var);
m_nodename = "";
m_node_name = "";
}
}
else
{
m_nodename = nodename_string;
m_node_name = nodename_string;
}
SPDLOG_INFO("metadata are received from nodename '{}'", m_nodename);
SPDLOG_DEBUG("metadata are received from node '{}'", m_node_name);
}
else
{
Expand Down Expand Up @@ -225,7 +225,7 @@ bool my_plugin::init(falcosecurity::init_input& in)
auto& t = in.tables();

// The default logger is already multithread.
// The initial verbosity is `warn`, after parsing the plugin config, this
// The initial verbosity is `info`, after parsing the plugin config, this
// value could change
spdlog::set_level(spdlog::level::info);

Expand All @@ -236,6 +236,10 @@ bool my_plugin::init(falcosecurity::init_input& in)
spdlog::set_pattern("%c: [%l] [k8smeta] %v");

SPDLOG_DEBUG("init the plugin");
// Remove this log when we reach `1.0.0`
SPDLOG_WARN("[EXPERIMENTAL] This plugin is in active development "
"and may undergo changes in behavior without prioritizing "
"backward compatibility.");

// This should never happen, the config is validated by the framework
if(in.get_config().empty())
Expand Down Expand Up @@ -310,14 +314,15 @@ void my_plugin::async_thread_loop(

while(!m_async_thread_quit.load())
{
K8sMetaClient k8sclient(m_nodename, ip_port, m_ca_PEM_encoding, m_mu,
K8sMetaClient k8sclient(m_node_name, ip_port, m_ca_PEM_encoding, m_mu,
m_cv, m_async_thread_quit, *h.get());

if(!k8sclient.Await(backoff_seconds))
{
break;
}

SPDLOG_INFO("Retry after '{}' seconds", backoff_seconds);
std::unique_lock<std::mutex> l(m_mu);
m_cv.wait_for(l, std::chrono::seconds(backoff_seconds),
[this] { return m_async_thread_quit.load(); });
Expand Down
2 changes: 1 addition & 1 deletion plugins/k8smeta/src/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class my_plugin
// Init params
std::string m_collector_hostname;
std::string m_collector_port;
std::string m_nodename;
std::string m_node_name;
std::string m_ca_PEM_encoding;

// State tables
Expand Down
2 changes: 1 addition & 1 deletion plugins/k8smeta/src/shared_with_tests_consts.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,5 @@ limitations under the License.
#define VERBOSITY_PATH "/verbosity"
#define HOSTNAME_PATH "/collectorHostname"
#define PORT_PATH "/collectorPort"
#define NODENAME_PATH "/nodename"
#define NODENAME_PATH "/nodeName"
#define CA_CERT_PATH "/caPEMBundle"
5 changes: 5 additions & 0 deletions plugins/k8smeta/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ To run the k8s plugin tests we use the libsinsp framework tests, in this way we
## Run tests

```bash
cd build
# build the test server which emulates the remote collector
make build-server
# run the test server
make run-server
# build tests
make build-tests
# run tests against the test server
make run-tests
```
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 @@ -20,7 +20,7 @@ limitations under the License.
// We can modify the log verbosity here.
#define INIT_CONFIG \
"{\"collectorHostname\":\"localhost\",\"collectorPort\": " \
"45000,\"nodename\":\"control-plane\",\"verbosity\":" \
"45000,\"nodeName\":\"control-plane\",\"verbosity\":" \
"\"info\"}"

#define ASSERT_STRING_SETS(a, b) \
Expand Down
6 changes: 3 additions & 3 deletions plugins/k8smeta/test/rules/example_rule.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- rule: Example rule for k8s plugin
- rule: Example rule for k8smeta plugin
desc: Detect execve events into pods
# we want to catch all execve events into a pod
# we want to catch all 'execve' events inside all pods
condition: evt.type = execve and k8smeta.pod.uid != ""
output: -> Triggered (pod_name=%k8smeta.pod.name pod_id=%k8smeta.pod.uid pod_ip=%k8smeta.pod.ip pod_namespace_name=%k8smeta.ns.name pod_deployment_name=%k8smeta.deployment.name pod_rs_name=%k8smeta.rs.name pod_services_names=%k8smeta.svc.name)
output: -> Triggered (pod_name=%k8smeta.pod.name pod_id=%k8smeta.pod.uid pod_namespace_name=%k8smeta.ns.name pod_deployment_name=%k8smeta.deployment.name pod_rs_name=%k8smeta.rs.name pod_services_names=%k8smeta.svc.name)
priority: WARNING
8 changes: 4 additions & 4 deletions plugins/k8smeta/test/src/init_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ TEST_F(sinsp_with_test_input, plugin_k8s_init_with_missing_required_argument)
ASSERT_TRUE(plugin_owner.get());
std::string err;

// The nodename is also a required argument, but here it is not provided
// The node name is also a required argument, but here it is not provided
ASSERT_THROW(plugin_owner->init("{\"collectorHostname\":\"localhost\","
"\"collectorPort\":\"45000\"}",
err),
Expand All @@ -58,7 +58,7 @@ TEST_F(sinsp_with_test_input, plugin_k8s_init_with_not_allowed_verbosity_value)

// `warn` is not a valid value for the `verbosity` field
ASSERT_THROW(plugin_owner->init("{\"collectorHostname\":\"localhost\","
"\"collectorPort\":\"45000\",\"nodename\":"
"\"collectorPort\":\"45000\",\"nodeName\":"
"\"control-plane\",\"verbosity\":\"warn\"}",
err),
sinsp_exception);
Expand All @@ -71,7 +71,7 @@ TEST_F(sinsp_with_test_input, plugin_k8s_with_simple_config)
std::string err;

ASSERT_NO_THROW(plugin_owner->init(R"(
{"collectorHostname":"localhost","collectorPort":45000,"nodename":"kind-control-plane"})",
{"collectorHostname":"localhost","collectorPort":45000,"nodeName":"kind-control-plane"})",
err));
ASSERT_EQ(err, "");
}
Expand All @@ -88,7 +88,7 @@ TEST_F(sinsp_with_test_input, plugin_k8s_env_variable)
setenv(env_var_name.c_str(), env_var_value.c_str(), 1);

ASSERT_NO_THROW(plugin_owner->init(R"(
{"collectorHostname":"localhost","collectorPort":45000,"nodename":" ${FALCO_NODE_NAME} "})",
{"collectorHostname":"localhost","collectorPort":45000,"nodeName":" ${FALCO_NODE_NAME} "})",
err));
ASSERT_EQ(err, "");
}
Loading