mirror of
https://github.com/falcosecurity/falco.git
synced 2025-08-01 22:47:46 +00:00
Add ability to read rules files from directories (#348)
* Add ability to read rules files from directories When the argument to -r <path> or an entry in falco.yaml's rules_file list is a directory, read all files in the directory and add them to the rules file list. The files in the directory are sorted alphabetically before being added to the list. The installed falco adds directories /etc/falco/rules.available and /etc/falco/rules.d and moves /etc/falco/application_rules.yaml to /etc/falco/rules.available. /etc/falco/rules.d is empty, but the idea is that admins can symlink to /etc/falco/rules.available for applications they want to enable. This will make it easier to add application-specific rulesets that admins can opt-in to. * Unit test for reading rules from directory Copy the rules/trace file from the test multiple_rules to a new test rules_directory. The rules files are in rules/rules_dir/{000,001}*.yaml, and the test uses a rules_file argument of rules_dir. Ensure that the same events are detected.
This commit is contained in:
parent
8389e44d7b
commit
c5b3097a65
@ -1,4 +1,4 @@
|
||||
/etc/falco/falco.yaml
|
||||
/etc/falco/falco_rules.yaml
|
||||
/etc/falco/application_rules.yaml
|
||||
/etc/falco/rules.available/application_rules.yaml
|
||||
/etc/falco/falco_rules.local.yaml
|
||||
|
@ -1,4 +1,7 @@
|
||||
# File(s) containing Falco rules, loaded at startup.
|
||||
# File(s) or Directories containing Falco rules, loaded at startup.
|
||||
# The name "rules_file" is only for backwards compatibility.
|
||||
# If the entry is a file, it will be read directly. If the entry is a directory,
|
||||
# every file in that directory will be read, in alphabetical order.
|
||||
#
|
||||
# falco_rules.yaml ships with the falco package and is overridden with
|
||||
# every new software version. falco_rules.local.yaml is only created
|
||||
@ -10,6 +13,7 @@
|
||||
rules_file:
|
||||
- /etc/falco/falco_rules.yaml
|
||||
- /etc/falco/falco_rules.local.yaml
|
||||
- /etc/falco/rules.d
|
||||
|
||||
# Whether to output events in json or text
|
||||
json_output: false
|
||||
|
@ -31,7 +31,9 @@ install(FILES falco_rules.local.yaml
|
||||
RENAME "${FALCO_LOCAL_RULES_DEST_FILENAME}")
|
||||
|
||||
install(FILES application_rules.yaml
|
||||
DESTINATION "${FALCO_ETC_DIR}"
|
||||
DESTINATION "/etc/falco/rules.available"
|
||||
RENAME "${FALCO_APP_RULES_DEST_FILENAME}")
|
||||
|
||||
install(DIRECTORY DESTINATION "/etc/falco/rules.d")
|
||||
endif()
|
||||
|
||||
|
@ -129,6 +129,16 @@ trace_files: !mux
|
||||
- rules/double_rule.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
rules_directory:
|
||||
detect: True
|
||||
detect_level:
|
||||
- WARNING
|
||||
- INFO
|
||||
- ERROR
|
||||
rules_file:
|
||||
- rules/rules_dir
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
multiple_rules_suppress_info:
|
||||
detect: True
|
||||
detect_level:
|
||||
|
14
test/rules/rules_dir/000-single_rule.yaml
Normal file
14
test/rules/rules_dir/000-single_rule.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
- list: cat_binaries
|
||||
items: [cat]
|
||||
|
||||
- list: cat_capable_binaries
|
||||
items: [cat_binaries]
|
||||
|
||||
- macro: is_cat
|
||||
condition: proc.name in (cat_capable_binaries)
|
||||
|
||||
- rule: open_from_cat
|
||||
desc: A process named cat does an open
|
||||
condition: evt.type=open and is_cat
|
||||
output: "An open was seen (command=%proc.cmdline)"
|
||||
priority: WARNING
|
13
test/rules/rules_dir/001-double_rule.yaml
Normal file
13
test/rules/rules_dir/001-double_rule.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
# This ruleset depends on the is_cat macro defined in single_rule.yaml
|
||||
|
||||
- rule: exec_from_cat
|
||||
desc: A process named cat does execve
|
||||
condition: evt.type=execve and is_cat
|
||||
output: "An exec was seen (command=%proc.cmdline)"
|
||||
priority: ERROR
|
||||
|
||||
- rule: access_from_cat
|
||||
desc: A process named cat does an access
|
||||
condition: evt.type=access and is_cat
|
||||
output: "An access was seen (command=%proc.cmdline)"
|
||||
priority: INFO
|
@ -16,6 +16,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "configuration.h"
|
||||
#include "logger.h"
|
||||
|
||||
@ -62,7 +69,7 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
|
||||
struct stat buffer;
|
||||
if(stat(file.c_str(), &buffer) == 0)
|
||||
{
|
||||
m_rules_filenames.push_back(file);
|
||||
read_rules_file_directory(file, m_rules_filenames);
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +157,70 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
|
||||
falco_logger::log_syslog = m_config->get_scalar<bool>("log_syslog", true);
|
||||
}
|
||||
|
||||
void falco_configuration::read_rules_file_directory(const string &path, list<string> &rules_filenames)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
int rc = stat(path.c_str(), &st);
|
||||
|
||||
if(rc != 0)
|
||||
{
|
||||
std::cerr << "Could not get info on rules file " << path << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if(st.st_mode & S_IFDIR)
|
||||
{
|
||||
// It's a directory. Read the contents, sort
|
||||
// alphabetically, and add every path to
|
||||
// rules_filenames
|
||||
vector<string> dir_filenames;
|
||||
|
||||
DIR *dir = opendir(path.c_str());
|
||||
|
||||
if(!dir)
|
||||
{
|
||||
std::cerr << "Could not get read contents of directory " << path << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
for (struct dirent *ent = readdir(dir); ent; ent = readdir(dir))
|
||||
{
|
||||
string efile = path + "/" + ent->d_name;
|
||||
|
||||
rc = stat(efile.c_str(), &st);
|
||||
|
||||
if(rc != 0)
|
||||
{
|
||||
std::cerr << "Could not get info on rules file " << efile << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if(st.st_mode & S_IFREG)
|
||||
{
|
||||
dir_filenames.push_back(efile);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
std::sort(dir_filenames.begin(),
|
||||
dir_filenames.end());
|
||||
|
||||
for (string &ent : dir_filenames)
|
||||
{
|
||||
rules_filenames.push_back(ent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume it's a file and just add to
|
||||
// rules_filenames. If it can't be opened/etc that
|
||||
// will be reported later..
|
||||
rules_filenames.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
static bool split(const string &str, char delim, pair<string,string> &parts)
|
||||
{
|
||||
size_t pos;
|
||||
|
@ -165,6 +165,8 @@ class falco_configuration
|
||||
void init(std::string conf_filename, std::list<std::string> &cmdline_options);
|
||||
void init(std::list<std::string> &cmdline_options);
|
||||
|
||||
static void read_rules_file_directory(const string &path, list<string> &rules_filenames);
|
||||
|
||||
std::list<std::string> m_rules_filenames;
|
||||
bool m_json_output;
|
||||
bool m_json_include_output_property;
|
||||
|
@ -106,8 +106,9 @@ static void usage()
|
||||
" of %%container.info in rule output fields\n"
|
||||
" See the examples section below for more info.\n"
|
||||
" -P, --pidfile <pid_file> When run as a daemon, write pid to specified file\n"
|
||||
" -r <rules_file> Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\n"
|
||||
" Can be specified multiple times to read from multiple files.\n"
|
||||
" -r <rules_file> Rules file/directory (defaults to value set in configuration file,\n"
|
||||
" or /etc/falco_rules.yaml). Can be specified multiple times to read\n"
|
||||
" from multiple files/directories.\n"
|
||||
" -s <stats_file> If specified, write statistics related to falco's reading/processing of events\n"
|
||||
" to this file. (Only useful in live mode).\n"
|
||||
" -T <tag> Disable any rules with a tag=<tag>. Can be specified multiple times.\n"
|
||||
@ -391,7 +392,7 @@ int falco_init(int argc, char **argv)
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
rules_filenames.push_back(optarg);
|
||||
falco_configuration::read_rules_file_directory(string(optarg), rules_filenames);
|
||||
break;
|
||||
case 's':
|
||||
stats_filename = optarg;
|
||||
@ -516,13 +517,19 @@ int falco_init(int argc, char **argv)
|
||||
|
||||
if(config.m_rules_filenames.size() == 0)
|
||||
{
|
||||
throw std::invalid_argument("You must specify at least one rules file via -r or a rules_file entry in falco.yaml");
|
||||
throw std::invalid_argument("You must specify at least one rules file/directory via -r or a rules_file entry in falco.yaml");
|
||||
}
|
||||
|
||||
falco_logger::log(LOG_DEBUG, "Configured rules filenames:\n");
|
||||
for (auto filename : config.m_rules_filenames)
|
||||
{
|
||||
falco_logger::log(LOG_DEBUG, string(" ") + filename + "\n");
|
||||
}
|
||||
|
||||
for (auto filename : config.m_rules_filenames)
|
||||
{
|
||||
falco_logger::log(LOG_INFO, "Loading rules from file " + filename + ":\n");
|
||||
engine->load_rules_file(filename, verbose, all_events);
|
||||
falco_logger::log(LOG_INFO, "Parsed rules from file " + filename + "\n");
|
||||
}
|
||||
|
||||
// You can't both disable and enable rules
|
||||
|
Loading…
Reference in New Issue
Block a user