Files
falco/userspace/engine/rules.cpp
Mark Stemm bbbac6203c Update rules loader to be general purpose, through factories
Update rules loader to be more general purpose by using factories and
the general purpose engine:

- A lua callback create_lua_parser creates a lua_parser with a filter
  object of the right type. The lua parser can then iterate the AST
  and populate the filter object.

- Like the falco engine, the rules loader is configured with a list of
  factories, and add_filter is now general purpose, taking a source.

Given the fix in https://github.com/falcosecurity/libs/pull/72, there
isn't any need to pass down the entire set of sinsp event
types/syscalls and validate that all filter event types are
valid. That job is now handled by the sinsp filter parsing
code. add_filter now returns the number of event types used by the new
filter, and if that number is excessive the lua code will return a
warning.

Format handling is mostly not handled by the rules loader any more. As
a convienence, there's a new lua callback is_format_valid which takes
a source and output string and uses the right formatter factory to
create a formatter. As long as that doesn't throw an exception, the
format is valid.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00

389 lines
9.0 KiB
C++

/*
Copyright (C) 2019 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <sstream>
#include "rules.h"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include "falco_engine.h"
#include "banned.h" // This raises a compilation error when certain functions are used
const static struct luaL_Reg ll_falco_rules[] =
{
{"clear_filters", &falco_rules::clear_filters},
{"create_lua_parser", &falco_rules::create_lua_parser},
{"add_filter", &falco_rules::add_filter},
{"enable_rule", &falco_rules::enable_rule},
{"engine_version", &falco_rules::engine_version},
{"is_format_valid", &falco_rules::is_format_valid},
{NULL, NULL}};
falco_rules::falco_rules(falco_engine *engine,
lua_State *ls)
: m_engine(engine),
m_ls(ls)
{
}
void falco_rules::add_filter_factory(const std::string &source,
std::shared_ptr<gen_event_filter_factory> factory)
{
m_filter_factories[source] = factory;
}
void falco_rules::init(lua_State *ls)
{
luaL_openlib(ls, "falco_rules", ll_falco_rules, 0);
lua_parser::register_callbacks(ls, "filter");
}
int falco_rules::clear_filters(lua_State *ls)
{
if (! lua_islightuserdata(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to clear_filters()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -1);
rules->clear_filters();
return 0;
}
void falco_rules::clear_filters()
{
m_engine->clear_filters();
}
int falco_rules::create_lua_parser(lua_State *ls)
{
if (! lua_islightuserdata(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid argument passed to create_lua_parser()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
std::string source = lua_tostring(ls, -1);
std::string errstr;
lua_parser *lp = rules->create_lua_parser(source, errstr);
if(lp == NULL) {
lua_pushstring(ls, errstr.c_str());
lua_error(ls);
}
lua_pushlightuserdata(ls, lp);
return 1;
}
lua_parser *falco_rules::create_lua_parser(std::string &source, std::string &errstr)
{
auto it = m_filter_factories.find(source);
if(it == m_filter_factories.end())
{
errstr = string("Unknown event source ") + source;
return NULL;
}
lua_parser *lp = new lua_parser(it->second);
return lp;
}
int falco_rules::add_filter(lua_State *ls)
{
if (! lua_islightuserdata(ls, -5) ||
! lua_islightuserdata(ls, -4) ||
! lua_isstring(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_istable(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to add_filter()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -5);
lua_parser *lp = (lua_parser *) lua_topointer(ls, -4);
std::string rule = lua_tostring(ls, -3);
std::string source = lua_tostring(ls, -2);
set<string> tags;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -2) != 0) {
// key is at index -2, value is at index
// -1. We want the values.
tags.insert(lua_tostring(ls, -1));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
size_t num_evttypes = lp->filter()->evttypes().size();
try {
rules->add_filter(lp->filter(), rule, source, tags);
} catch (exception &e) {
std::string errstr = string("Could not add rule to falco engine: ") + e.what();
lua_pushstring(ls, errstr.c_str());
lua_error(ls);
}
delete lp;
lua_pushnumber(ls, num_evttypes);
return 1;
}
void falco_rules::add_filter(std::shared_ptr<gen_event_filter> filter, string &rule, string &source, set<string> &tags)
{
m_engine->add_filter(filter, rule, source, tags);
}
int falco_rules::enable_rule(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isnumber(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to enable_rule()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
const char *rulec = lua_tostring(ls, -2);
std::string rule = rulec;
bool enabled = (lua_tonumber(ls, -1) ? true : false);
rules->enable_rule(rule, enabled);
return 0;
}
void falco_rules::enable_rule(string &rule, bool enabled)
{
m_engine->enable_rule(rule, enabled);
}
int falco_rules::engine_version(lua_State *ls)
{
if (! lua_islightuserdata(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to engine_version()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -1);
lua_pushnumber(ls, rules->m_engine->engine_version());
return 1;
}
int falco_rules::is_format_valid(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_format_valid");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
string source = luaL_checkstring(ls, -2);
string format = luaL_checkstring(ls, -1);
string errstr;
bool ret = rules->is_format_valid(source, format, errstr);
if (!ret)
{
lua_pushstring(ls, errstr.c_str());
}
else
{
lua_pushnil(ls);
}
return 1;
}
bool falco_rules::is_format_valid(const std::string &source, const std::string &format, std::string &errstr)
{
bool ret = true;
try
{
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_engine->create_formatter(source, format);
}
catch(exception &e)
{
std::ostringstream os;
os << "Invalid output format '"
<< format
<< "': '"
<< e.what()
<< "'";
errstr = os.str();
ret = false;
}
return ret;
}
static std::list<std::string> get_lua_table_values(lua_State *ls, int idx)
{
std::list<std::string> ret;
if (lua_isnil(ls, idx)) {
return ret;
}
lua_pushnil(ls); /* first key */
while (lua_next(ls, idx-1) != 0) {
// key is at index -2, value is at index
// -1. We want the values.
if (! lua_isstring(ls, -1)) {
std::string err = "Non-string value in table of strings";
throw falco_exception(err);
}
ret.push_back(string(lua_tostring(ls, -1)));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
return ret;
}
void falco_rules::load_rules(const string &rules_content,
bool verbose, bool all_events,
string &extra, bool replace_container_info,
falco_common::priority_type min_priority,
uint64_t &required_engine_version)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
{
lua_pushstring(m_ls, rules_content.c_str());
lua_pushlightuserdata(m_ls, this);
lua_pushboolean(m_ls, (verbose ? 1 : 0));
lua_pushboolean(m_ls, (all_events ? 1 : 0));
lua_pushstring(m_ls, extra.c_str());
lua_pushboolean(m_ls, (replace_container_info ? 1 : 0));
lua_pushnumber(m_ls, min_priority);
if(lua_pcall(m_ls, 7, 4, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error loading rules: " + string(lerr);
throw falco_exception(err);
}
// Returns:
// Load result: bool
// required engine version: will be nil when load result is false
// array of errors
// array of warnings
bool successful = lua_toboolean(m_ls, -4);
required_engine_version = lua_tonumber(m_ls, -3);
std::list<std::string> errors = get_lua_table_values(m_ls, -2);
std::list<std::string> warnings = get_lua_table_values(m_ls, -1);
// Concatenate errors/warnings
std::ostringstream os;
if (errors.size() > 0)
{
os << errors.size() << " errors:" << std::endl;
for(auto err : errors)
{
os << err << std::endl;
}
}
if (warnings.size() > 0)
{
os << warnings.size() << " warnings:" << std::endl;
for(auto warn : warnings)
{
os << warn << std::endl;
}
}
if(!successful)
{
throw falco_exception(os.str());
}
if (verbose && os.str() != "") {
// We don't really have a logging callback
// from the falco engine, but this would be a
// good place to use it.
fprintf(stderr, "When reading rules content: %s", os.str().c_str());
}
lua_pop(m_ls, 4);
} else {
throw falco_exception("No function " + m_lua_load_rules + " found in lua rule module");
}
}
void falco_rules::describe_rule(std::string *rule)
{
lua_getglobal(m_ls, m_lua_describe_rule.c_str());
if(lua_isfunction(m_ls, -1))
{
if (rule == NULL)
{
lua_pushnil(m_ls);
} else {
lua_pushstring(m_ls, rule->c_str());
}
if(lua_pcall(m_ls, 1, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Could not describe " + (rule == NULL ? "all rules" : "rule " + *rule) + ": " + string(lerr);
throw falco_exception(err);
}
} else {
throw falco_exception("No function " + m_lua_describe_rule + " found in lua rule module");
}
}
falco_rules::~falco_rules()
{
}