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

Flag excess drops #561

Merged
merged 6 commits into from
Mar 27, 2019
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
20 changes: 20 additions & 0 deletions falco.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ priority: debug
# buffered. Defaults to false
buffered_outputs: false

# Falco uses a shared buffer between the kernel and userspace to pass
# system call information. When falco detects that this buffer is
# full and system calls have been dropped, it can take one or more of
# the following actions:
# - "ignore": do nothing. If an empty list is provided, ignore is assumed.
# - "log": log a CRITICAL message noting that the buffer was full.
# - "alert": emit a falco alert noting that the buffer was full.
# - "exit": exit falco with a non-zero rc.
#
# The rate at which log/alert messages are emitted is governed by a
# token bucket. The rate corresponds to one message every 30 seconds
# with a burst of 10 messages.

syscall_event_drops:
actions:
- log
- alert
rate: .03333
max_burst: 10

# A throttling mechanism implemented as a token bucket limits the
# rate of falco notifications. This throttling is controlled by the following configuration
# options:
Expand Down
11 changes: 11 additions & 0 deletions test/confs/drops_alert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syscall_event_drops:
actions:
- alert
rate: .03333
max_burst: 10
simulate_drops: true

stdout_output:
enabled: true

log_stderr: true
11 changes: 11 additions & 0 deletions test/confs/drops_exit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syscall_event_drops:
actions:
- exit
rate: .03333
max_burst: 10
simulate_drops: true

stdout_output:
enabled: true

log_stderr: true
11 changes: 11 additions & 0 deletions test/confs/drops_ignore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syscall_event_drops:
actions:
- ignore
rate: .03333
max_burst: 10
simulate_drops: true

stdout_output:
enabled: true

log_stderr: true
11 changes: 11 additions & 0 deletions test/confs/drops_log.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syscall_event_drops:
actions:
- log
rate: .03333
max_burst: 10
simulate_drops: true

stdout_output:
enabled: true

log_stderr: true
11 changes: 11 additions & 0 deletions test/confs/drops_none.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syscall_event_drops:
actions:
- log
rate: .03333
max_burst: 10
simulate_drops: false

stdout_output:
enabled: true

log_stderr: true
46 changes: 40 additions & 6 deletions test/falco_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,31 @@ def setUp(self):
self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, '../build'))

self.stdout_contains = self.params.get('stdout_contains', '*', default='')

if not isinstance(self.stdout_contains, list):
self.stdout_contains = [self.stdout_contains]

self.stderr_contains = self.params.get('stderr_contains', '*', default='')

if not isinstance(self.stderr_contains, list):
self.stderr_contains = [self.stderr_contains]

self.stdout_not_contains = self.params.get('stdout_not_contains', '*', default='')

if not isinstance(self.stdout_not_contains, list):
if self.stdout_not_contains == '':
self.stdout_not_contains = []
else:
self.stdout_not_contains = [self.stdout_not_contains]

self.stderr_not_contains = self.params.get('stderr_not_contains', '*', default='')

if not isinstance(self.stderr_not_contains, list):
if self.stderr_not_contains == '':
self.stderr_not_contains = []
else:
self.stderr_not_contains = [self.stderr_not_contains]

self.exit_status = self.params.get('exit_status', '*', default=0)
self.should_detect = self.params.get('detect', '*', default=False)
self.trace_file = self.params.get('trace_file', '*', default='')
Expand Down Expand Up @@ -389,15 +413,25 @@ def test(self):

res = self.falco_proc.run(timeout=180, sig=9)

if self.stderr_contains != '':
match = re.search(self.stderr_contains, res.stderr)
for pattern in self.stderr_contains:
match = re.search(pattern, res.stderr)
if match is None:
self.fail("Stderr of falco process did not contain content matching {}".format(self.stderr_contains))
self.fail("Stderr of falco process did not contain content matching {}".format(pattern))

if self.stdout_contains != '':
match = re.search(self.stdout_contains, res.stdout)
for pattern in self.stdout_contains:
match = re.search(pattern, res.stdout)
if match is None:
self.fail("Stdout of falco process '{}' did not contain content matching {}".format(res.stdout, self.stdout_contains))
self.fail("Stdout of falco process '{}' did not contain content matching {}".format(res.stdout, pattern))

for pattern in self.stderr_not_contains:
match = re.search(pattern, res.stderr)
if match is not None:
self.fail("Stderr of falco process contained content matching {} when it should have not".format(pattern))

for pattern in self.stdout_not_contains:
match = re.search(pattern, res.stdout)
if match is not None:
self.fail("Stdout of falco process '{}' did contain content matching {} when it should have not".format(res.stdout, pattern))

if res.exit_status != self.exit_status:
self.error("Falco command \"{}\" exited with unexpected return value {} (!= {})".format(
Expand Down
71 changes: 70 additions & 1 deletion test/falco_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -770,4 +770,73 @@ trace_files: !mux
stderr_contains: Rules require engine version 9999999, but engine version is
rules_file:
- rules/engine_version_mismatch.yaml
trace_file: trace_files/cat_write.scap
trace_file: trace_files/cat_write.scap

monitor_syscall_drops_none:
exit_status: 0
rules_file:
- rules/single_rule.yaml
conf_file: confs/drops_none.yaml
trace_file: trace_files/ping_sendto.scap
stderr_not_contains:
- "event drop detected: 9 occurrences"
- "num times actions taken: 9"
- "Falco internal: syscall event drop"
stdout_not_contains:
- "Falco internal: syscall event drop"

monitor_syscall_drops_ignore:
exit_status: 0
rules_file:
- rules/single_rule.yaml
conf_file: confs/drops_ignore.yaml
trace_file: trace_files/ping_sendto.scap
stderr_contains:
- "event drop detected: 9 occurrences"
- "num times actions taken: 9"
stderr_not_contains:
- "Falco internal: syscall event drop"
stdout_not_contains:
- "Falco internal: syscall event drop"

monitor_syscall_drops_log:
exit_status: 0
rules_file:
- rules/single_rule.yaml
conf_file: confs/drops_log.yaml
trace_file: trace_files/ping_sendto.scap
stderr_contains:
- "event drop detected: 9 occurrences"
- "num times actions taken: 9"
- "Falco internal: syscall event drop"
stdout_not_contains:
- "Falco internal: syscall event drop"

monitor_syscall_drops_alert:
exit_status: 0
rules_file:
- rules/single_rule.yaml
conf_file: confs/drops_alert.yaml
trace_file: trace_files/ping_sendto.scap
stderr_contains:
- "event drop detected: 9 occurrences"
- "num times actions taken: 9"
stderr_not_contains:
- "Falco internal: syscall event drop"
stdout_contains:
- "Falco internal: syscall event drop"

monitor_syscall_drops_exit:
exit_status: 1
rules_file:
- rules/single_rule.yaml
conf_file: confs/drops_exit.yaml
trace_file: trace_files/ping_sendto.scap
stderr_contains:
- "event drop detected: 1 occurrences"
- "num times actions taken: 1"
- "Falco internal: syscall event drop"
- "Exiting."
stdout_not_contains:
- "Falco internal: syscall event drop"

Binary file added test/trace_files/ping_sendto.scap
Binary file not shown.
1 change: 1 addition & 0 deletions userspace/falco/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ add_executable(falco
configuration.cpp
logger.cpp
falco_outputs.cpp
event_drops.cpp
statsfilewriter.cpp
falco.cpp
"${PROJECT_SOURCE_DIR}/../sysdig/userspace/sysdig/fields_info.cpp"
Expand Down
37 changes: 37 additions & 0 deletions userspace/falco/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,43 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
m_webserver_k8s_audit_endpoint = m_config->get_scalar<string>("webserver", "k8s_audit_endpoint", "/k8s_audit");
m_webserver_ssl_enabled = m_config->get_scalar<bool>("webserver", "ssl_enabled", false);
m_webserver_ssl_certificate = m_config->get_scalar<string>("webserver", "ssl_certificate","/etc/falco/falco.pem");

std::list<string> syscall_event_drop_acts;
m_config->get_sequence(syscall_event_drop_acts, "syscall_event_drops", "actions");

for(std::string &act : syscall_event_drop_acts)
{
if(act == "ignore")
{
m_syscall_evt_drop_actions.insert(syscall_evt_drop_mgr::ACT_IGNORE);
}
else if (act == "log")
{
m_syscall_evt_drop_actions.insert(syscall_evt_drop_mgr::ACT_LOG);
}
else if (act == "alert")
{
m_syscall_evt_drop_actions.insert(syscall_evt_drop_mgr::ACT_ALERT);
}
else if (act == "exit")
{
m_syscall_evt_drop_actions.insert(syscall_evt_drop_mgr::ACT_EXIT);
}
else
{
throw invalid_argument("Error reading config file (" + m_config_file + "): syscall event drop action " + act + " must be one of \"ignore\", \"log\", \"alert\", or \"exit\"");
}
}

if(m_syscall_evt_drop_actions.empty())
{
m_syscall_evt_drop_actions.insert(syscall_evt_drop_mgr::ACT_IGNORE);
}

m_syscall_evt_drop_rate = m_config->get_scalar<double>("syscall_event_drops", "rate", 0.3333);
m_syscall_evt_drop_max_burst = m_config->get_scalar<double>("syscall_event_drops", "max_burst", 10);

m_syscall_evt_simulate_drops = m_config->get_scalar<bool>("syscall_event_drops", "simulate_drops", false);
}

void falco_configuration::read_rules_file_directory(const string &path, list<string> &rules_filenames)
Expand Down
48 changes: 41 additions & 7 deletions userspace/falco/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ limitations under the License.
#include <string>
#include <vector>
#include <list>
#include <set>
#include <iostream>

#include "event_drops.h"
#include "falco_outputs.h"

class yaml_configuration
Expand Down Expand Up @@ -133,23 +135,48 @@ class yaml_configuration

// called with the last variadic arg (where the sequence is expected to be found)
template <typename T>
void get_sequence(T& ret, const std::string& name)
void get_sequence_from_node(T& ret, const YAML::Node &node)
{
YAML::Node child_node = m_root[name];
if(child_node.IsDefined())
if(node.IsDefined())
{
if(child_node.IsSequence())
if(node.IsSequence())
{
for(const YAML::Node& item : child_node)
for(const YAML::Node& item : node)
{
ret.insert(ret.end(), item.as<typename T::value_type>());
}
}
else if(child_node.IsScalar())
else if(node.IsScalar())
{
ret.insert(ret.end(), node.as<typename T::value_type>());
}
}
}

// called with the last variadic arg (where the sequence is expected to be found)
template <typename T>
void get_sequence(T& ret, const std::string& name)
{
return get_sequence_from_node<T>(ret, m_root[name]);
}

// called with the last variadic arg (where the sequence is expected to be found)
template <typename T>
void get_sequence(T& ret, const std::string& key, const std::string &subkey)
{
try
{
auto node = m_root[key];
if (node.IsDefined())
{
ret.insert(ret.end(), child_node.as<typename T::value_type>());
return get_sequence_from_node<T>(ret, node[subkey]);
}
}
catch (const YAML::BadConversion& ex)
{
std::cerr << "Cannot read config file (" + m_path + "): wrong type at key " + key + "\n";
throw;
}
}

private:
Expand Down Expand Up @@ -184,6 +211,13 @@ class falco_configuration
std::string m_webserver_k8s_audit_endpoint;
bool m_webserver_ssl_enabled;
std::string m_webserver_ssl_certificate;
std::set<syscall_evt_drop_mgr::action> m_syscall_evt_drop_actions;
double m_syscall_evt_drop_rate;
double m_syscall_evt_drop_max_burst;

// Only used for testing
bool m_syscall_evt_simulate_drops;


private:
void init_cmdline_options(std::list<std::string> &cmdline_options);
Expand Down
Loading