Compare commits

..

2 Commits

Author SHA1 Message Date
Mark Stemm
b6f08cc403 Merge pull request #109 from draios/dev
Merging for 0.3.0
2016-08-05 12:35:31 -07:00
Mark Stemm
8d181e9051 Merge pull request #92 from draios/dev
Merging for 0.2.0
2016-06-09 10:40:20 -07:00
55 changed files with 555 additions and 2083 deletions

8
.gitignore vendored
View File

@@ -6,14 +6,6 @@ test/traces-negative
test/traces-positive
test/traces-info
test/job-results
test/.phoronix-test-suite
test/results*.json.*
userspace/falco/lua/re.lua
userspace/falco/lua/lpeg.so
docker/event-generator/event-generator
docker/event-generator/mysqld
docker/event-generator/httpd
docker/event-generator/sha1sum
docker/event-generator/vipw

View File

@@ -6,8 +6,8 @@ if(NOT DEFINED FALCO_VERSION)
set(FALCO_VERSION "0.1.1dev")
endif()
if(NOT DEFINED FALCO_ETC_DIR)
set(FALCO_ETC_DIR "/etc")
if(NOT DEFINED DIR_ETC)
set(DIR_ETC "/etc")
endif()
if(NOT CMAKE_BUILD_TYPE)
@@ -39,7 +39,6 @@ set(PACKAGE_NAME "falco")
set(PROBE_VERSION "${FALCO_VERSION}")
set(PROBE_NAME "sysdig-probe")
set(PROBE_DEVICE_NAME "sysdig")
set(CMAKE_INSTALL_PREFIX /usr)
set(CMD_MAKE make)
@@ -161,12 +160,11 @@ ExternalProject_Add(luajit
INSTALL_COMMAND "")
set (LPEG_SRC "${PROJECT_BINARY_DIR}/lpeg-prefix/src/lpeg")
set (LPEG_LIB "${PROJECT_BINARY_DIR}/lpeg-prefix/src/lpeg/build/lpeg.a")
ExternalProject_Add(lpeg
DEPENDS luajit
URL "http://s3.amazonaws.com/download.draios.com/dependencies/lpeg-1.0.0.tar.gz"
URL_MD5 "0aec64ccd13996202ad0c099e2877ece"
BUILD_COMMAND LUA_INCLUDE=${LUAJIT_INCLUDE} "${PROJECT_SOURCE_DIR}/scripts/build-lpeg.sh" "${LPEG_SRC}/build"
BUILD_COMMAND LUA_INCLUDE=${LUAJIT_INCLUDE} "${PROJECT_SOURCE_DIR}/scripts/build-lpeg.sh"
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ""
INSTALL_COMMAND "")
@@ -190,19 +188,17 @@ ExternalProject_Add(lyaml
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./configure --enable-static LIBS=-L../../../libyaml-prefix/src/libyaml/src/.libs CFLAGS=-I../../../libyaml-prefix/src/libyaml/include CPPFLAGS=-I../../../libyaml-prefix/src/libyaml/include LUA_INCLUDE=-I../../../luajit-prefix/src/luajit/src LUA=../../../luajit-prefix/src/luajit/src/luajit
INSTALL_COMMAND sh -c "cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/engine/lua")
INSTALL_COMMAND sh -c "cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/falco/lua")
install(FILES falco.yaml
DESTINATION "${FALCO_ETC_DIR}")
DESTINATION "${DIR_ETC}")
add_subdirectory("${SYSDIG_DIR}/driver" "${PROJECT_BINARY_DIR}/driver")
add_subdirectory("${SYSDIG_DIR}/userspace/libscap" "${PROJECT_BINARY_DIR}/userspace/libscap")
add_subdirectory("${SYSDIG_DIR}/userspace/libsinsp" "${PROJECT_BINARY_DIR}/userspace/libsinsp")
add_subdirectory(rules)
add_subdirectory(scripts)
set(FALCO_SINSP_LIBRARY sinsp)
set(FALCO_SHARE_DIR share/falco)
add_subdirectory(userspace/engine)
add_subdirectory(userspace/falco)

View File

@@ -14,15 +14,12 @@ RUN cp /etc/skel/.bashrc /root && cp /etc/skel/.profile /root
ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
RUN echo "deb http://httpredir.debian.org/debian jessie main" > /etc/apt/sources.list.d/jessie.list \
&& apt-get update \
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash-completion \
curl \
gnupg2 \
ca-certificates \
gcc \
gcc-5 \
gcc-4.9 && rm -rf /var/lib/apt/lists/*
# Terribly terrible hacks: since our base Debian image ships with GCC 5.0 which breaks older kernels,

View File

@@ -1,6 +0,0 @@
FROM alpine:latest
RUN apk add --no-cache bash g++
COPY ./event_generator.cpp /usr/local/bin
RUN mkdir -p /var/lib/rpm
RUN g++ --std=c++0x /usr/local/bin/event_generator.cpp -o /usr/local/bin/event_generator
CMD ["/usr/local/bin/event_generator"]

View File

@@ -1,2 +0,0 @@
image:
docker build -t sysdig/falco-event-generator:latest .

View File

@@ -1,420 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <utility>
#include <map>
#include <set>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <pwd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
using namespace std;
void usage(char *program)
{
printf("Usage %s [options]\n\n", program);
printf("Options:\n");
printf(" -h/--help: show this help\n");
printf(" -a/--action: actions to perform. Can be one of the following:\n");
printf(" write_binary_dir Write to files below /bin\n");
printf(" write_etc Write to files below /etc\n");
printf(" read_sensitive_file Read a sensitive file\n");
printf(" read_sensitive_file_after_startup As a trusted program, wait a while,\n");
printf(" then read a sensitive file\n");
printf(" write_rpm_database Write to files below /var/lib/rpm\n");
printf(" spawn_shell Run a shell (bash)\n");
printf(" db_program_spawn_process As a database program, try to spawn\n");
printf(" another program\n");
printf(" modify_binary_dirs Modify a file below /bin\n");
printf(" mkdir_binary_dirs Create a directory below /bin\n");
printf(" change_thread_namespace Change namespace\n");
printf(" system_user_interactive Change to a system user and try to\n");
printf(" run an interactive command\n");
printf(" network_activity Open network connections\n");
printf(" (used by system_procs_network_activity below)\n");
printf(" system_procs_network_activity Open network connections as a program\n");
printf(" that should not perform network actions\n");
printf(" non_sudo_setuid Setuid as a non-root user\n");
printf(" create_files_below_dev Create files below /dev\n");
printf(" exec_ls execve() the program ls\n");
printf(" (used by user_mgmt_binaries below)\n");
printf(" user_mgmt_binaries Become the program \"vipw\", which triggers\n");
printf(" rules related to user management programs\n");
printf(" all All of the above\n");
printf(" -i/--interval: Number of seconds between actions\n");
printf(" -o/--once: Perform actions once and exit\n");
}
void open_file(const char *filename, const char *flags)
{
FILE *f = fopen(filename, flags);
if(f)
{
fclose(f);
}
else
{
fprintf(stderr, "Could not open %s for writing: %s\n", filename, strerror(errno));
}
}
void touch(const char *filename)
{
open_file(filename, "w");
}
void read(const char *filename)
{
open_file(filename, "r");
}
uid_t become_user(const char *user)
{
struct passwd *pw;
pw = getpwnam(user);
if(pw == NULL)
{
fprintf(stderr, "Could not find user information for \"%s\" user: %s\n", user, strerror(errno));
exit(1);
}
int rc = setuid(pw->pw_uid);
if(rc != 0)
{
fprintf(stderr, "Could not change user to \"%s\" (uid %u): %s\n", user, pw->pw_uid, strerror(errno));
exit(1);
}
}
void spawn(const char *cmd, char **argv, char **env)
{
pid_t child;
// Fork a process, that way proc.duration is reset
if ((child = fork()) == 0)
{
execve(cmd, argv, env);
fprintf(stderr, "Could not exec to spawn %s: %s\n", cmd, strerror(errno));
}
else
{
int status;
waitpid(child, &status, 0);
}
}
void respawn(const char *cmd, const char *action, const char *interval)
{
char *argv[] = {(char *) cmd,
(char *) "--action", (char *) action,
(char *) "--interval", (char *) interval,
(char *) "--once", NULL};
char *env[] = {NULL};
spawn(cmd, argv, env);
}
void write_binary_dir() {
printf("Writing to /bin/created-by-event-generator-sh...\n");
touch("/bin/created-by-event-generator-sh");
}
void write_etc() {
printf("Writing to /etc/created-by-event-generator-sh...\n");
touch("/etc/created-by-event-generator-sh");
}
void read_sensitive_file() {
printf("Reading /etc/shadow...\n");
read("/etc/shadow");
}
void read_sensitive_file_after_startup() {
printf("Becoming the program \"httpd\", sleeping 6 seconds and reading /etc/shadow...\n");
respawn("./httpd", "read_sensitive_file", "6");
}
void write_rpm_database() {
printf("Writing to /var/lib/rpm/created-by-event-generator-sh...\n");
touch("/var/lib/rpm/created-by-event-generator-sh");
}
void spawn_shell() {
printf("Spawning a shell using system()...\n");
int rc;
if ((rc = system("ls > /dev/null")) != 0)
{
fprintf(stderr, "Could not run ls > /dev/null in a shell: %s\n", strerror(errno));
}
}
void db_program_spawn_process() {
printf("Becoming the program \"mysql\" and then spawning a shell\n");
respawn("./mysqld", "spawn_shell", "0");
}
void modify_binary_dirs() {
printf("Moving /bin/true to /bin/true.event-generator-sh and back...\n");
if (rename("/bin/true", "/bin/true.event-generator-sh") != 0)
{
fprintf(stderr, "Could not rename \"/bin/true\" to \"/bin/true.event-generator-sh\": %s\n", strerror(errno));
}
else
{
if (rename("/bin/true.event-generator-sh", "/bin/true") != 0)
{
fprintf(stderr, "Could not rename \"/bin/true.event-generator-sh\" to \"/bin/true\": %s\n", strerror(errno));
}
}
}
void mkdir_binary_dirs() {
printf("Creating directory /bin/directory-created-by-event-generator-sh...\n");
if (mkdir("/bin/directory-created-by-event-generator-sh", 0644) != 0)
{
fprintf(stderr, "Could not create directory \"/bin/directory-created-by-event-generator-sh\": %s\n", strerror(errno));
}
}
void change_thread_namespace() {
printf("Calling setns() to change namespaces...\n");
// It doesn't matter that the arguments to setns are
// bogus. It's the attempt to call it that will trigger the
// rule.
setns(0, 0);
}
void system_user_interactive() {
pid_t child;
// Fork a child and do everything in the child.
if ((child = fork()) == 0)
{
become_user("daemon");
char *argv[] = {(char *)"/bin/login", NULL};
char *env[] = {NULL};
spawn("/bin/login", argv, env);
exit(0);
}
else
{
int status;
waitpid(child, &status, 0);
}
}
void network_activity() {
printf("Opening a listening socket on port 8192...\n");
int rc;
int sock = socket(PF_INET, SOCK_DGRAM, 0);
struct sockaddr_in localhost;
localhost.sin_family = AF_INET;
localhost.sin_port = htons(8192);
inet_aton("127.0.0.1", &(localhost.sin_addr));
if((rc = bind(sock, (struct sockaddr *) &localhost, sizeof(localhost))) != 0)
{
fprintf(stderr, "Could not bind listening socket to localhost: %s\n", strerror(errno));
return;
}
listen(sock, 1);
close(sock);
}
void system_procs_network_activity() {
printf("Becoming the program \"sha1sum\" and then performing network activity\n");
respawn("./sha1sum", "network_activity", "0");
}
void non_sudo_setuid() {
pid_t child;
// Fork a child and do everything in the child.
if ((child = fork()) == 0)
{
// First setuid to something non-root. Then try to setuid back to root.
become_user("daemon");
become_user("root");
exit(0);
}
else
{
int status;
waitpid(child, &status, 0);
}
}
void create_files_below_dev() {
printf("Creating /dev/created-by-event-generator-sh...\n");
touch("/dev/created-by-event-generator-sh");
}
void exec_ls()
{
char *argv[] = {(char *)"/bin/ls", NULL};
char *env[] = {NULL};
spawn("/bin/ls", argv, env);
}
void user_mgmt_binaries() {
printf("Becoming the program \"vipw\" and then running the program /bin/ls\n");
printf("NOTE: does not result in a falco notification in containers\n");
respawn("./vipw", "exec_ls", "0");
}
typedef void (*action_t)();
map<string, action_t> defined_actions = {{"write_binary_dir", write_binary_dir},
{"write_etc", write_etc},
{"read_sensitive_file", read_sensitive_file},
{"read_sensitive_file_after_startup", read_sensitive_file_after_startup},
{"write_rpm_database", write_rpm_database},
{"spawn_shell", spawn_shell},
{"db_program_spawn_process", db_program_spawn_process},
{"modify_binary_dirs", modify_binary_dirs},
{"mkdir_binary_dirs", mkdir_binary_dirs},
{"change_thread_namespace", change_thread_namespace},
{"system_user_interactive", system_user_interactive},
{"network_activity", network_activity},
{"system_procs_network_activity", system_procs_network_activity},
{"non_sudo_setuid", non_sudo_setuid},
{"create_files_below_dev", create_files_below_dev},
{"exec_ls", exec_ls},
{"user_mgmt_binaries", user_mgmt_binaries}};
void create_symlinks(const char *program)
{
int rc;
// Some actions depend on this program being re-run as
// different program names like 'mysqld', 'httpd', etc. This
// sets up all the required symlinks.
const char *progs[] = {"./httpd", "./mysqld", "./sha1sum", "./vipw", NULL};
for (unsigned int i=0; progs[i] != NULL; i++)
{
unlink(progs[i]);
if ((rc = symlink(program, progs[i])) != 0)
{
fprintf(stderr, "Could not link \"./event_generator\" to \"%s\": %s\n", progs[i], strerror(errno));
}
}
}
void run_actions(map<string, action_t> &actions, int interval, bool once)
{
while (true)
{
for (auto action : actions)
{
sleep(interval);
printf("***Action %s\n", action.first.c_str());
action.second();
}
if(once)
{
break;
}
}
}
int main(int argc, char **argv)
{
map<string, action_t> actions;
int op;
int long_index = 0;
int interval = 1;
bool once = false;
map<string, action_t>::iterator it;
static struct option long_options[] =
{
{"help", no_argument, 0, 'h' },
{"action", required_argument, 0, 'a' },
{"interval", required_argument, 0, 'i' },
{"once", no_argument, 0, 'o' },
{0, 0}
};
//
// Parse the args
//
while((op = getopt_long(argc, argv,
"ha:i:l:",
long_options, &long_index)) != -1)
{
switch(op)
{
case 'h':
usage(argv[0]);
exit(1);
case 'a':
if((it = defined_actions.find(optarg)) == defined_actions.end())
{
fprintf(stderr, "No action with name \"%s\" known, exiting.\n", optarg);
exit(1);
}
actions.insert(*it);
break;
case 'i':
interval = atoi(optarg);
break;
case 'o':
once = true;
break;
default:
usage(argv[0]);
exit(1);
}
}
if(actions.size() == 0)
{
actions = defined_actions;
}
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
// Only create symlinks when running as the program event_generator
if (strstr(argv[0], "generator"))
{
create_symlinks(argv[0]);
}
run_actions(actions, interval, once);
}

View File

@@ -14,15 +14,12 @@ RUN cp /etc/skel/.bashrc /root && cp /etc/skel/.profile /root
ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
RUN echo "deb http://httpredir.debian.org/debian jessie main" > /etc/apt/sources.list.d/jessie.list \
&& apt-get update \
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash-completion \
curl \
ca-certificates \
gnupg2 \
gcc \
gcc-5 \
gcc-4.9 && rm -rf /var/lib/apt/lists/*
# Terribly terrible hacks: since our base Debian image ships with GCC 5.0 which breaks older kernels,

View File

@@ -14,8 +14,8 @@ router.get('/', function(req, res) {
});
router.get('/exec/:cmd', function(req, res) {
var ret = child_process.spawnSync(req.params.cmd);
res.send(ret.stdout);
var output = child_process.execSync(req.params.cmd);
res.send(output);
});
app.use('/api', router);

View File

@@ -1,19 +1,3 @@
if(NOT DEFINED FALCO_ETC_DIR)
set(FALCO_ETC_DIR "/etc")
endif()
if(NOT DEFINED FALCO_RULES_DEST_FILENAME)
set(FALCO_RULES_DEST_FILENAME "falco_rules.yaml")
endif()
if(DEFINED FALCO_COMPONENT)
install(FILES falco_rules.yaml
COMPONENT "${FALCO_COMPONENT}"
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_RULES_DEST_FILENAME}")
else()
install(FILES falco_rules.yaml
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_RULES_DEST_FILENAME}")
endif()
DESTINATION "${DIR_ETC}")

View File

@@ -54,12 +54,6 @@
- macro: linux_so_dirs
condition: ubuntu_so_dirs or centos_so_dirs or fd.name=/etc/ld.so.cache
- list: shell_binaries
items: [bash, csh, ksh, sh, tcsh, zsh, dash]
- macro: shell_procs
condition: proc.name in (shell_binaries)
- list: coreutils_binaries
items: [
truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who,
@@ -99,7 +93,7 @@
items: [setup-backend, dragent]
- list: docker_binaries
items: [docker, dockerd, exe]
items: [docker, exe]
- list: http_server_binaries
items: [nginx, httpd, httpd-foregroun, lighttpd]
@@ -167,7 +161,7 @@
- macro: container
condition: container.id != host
- macro: interactive
condition: ((proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind or proc.name=login)
condition: ((proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind)
- macro: syslog
condition: fd.name in (/dev/log, /run/systemd/journal/syslog)
- list: cron_binaries
@@ -183,7 +177,7 @@
# General Rules
###############
- rule: Write below binary dir
- rule: write_binary_dir
desc: an attempt to write to any file below a set of binary directories
condition: bin_dir and evt.dir = < and open_write and not package_mgmt_procs
output: "File below a known binary directory opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
@@ -192,55 +186,55 @@
- macro: write_etc_common
condition: >
etc_dir and evt.dir = < and open_write
and not proc.name in (shadowutils_binaries, sysdigcloud_binaries, package_mgmt_binaries, ssl_mgmt_binaries, dhcp_binaries, ldconfig.real, ldconfig)
and not proc.name in (shadowutils_binaries, sysdigcloud_binaries, package_mgmt_binaries, ssl_mgmt_binaries, dhcp_binaries, ldconfig.real)
and not proc.pname in (sysdigcloud_binaries)
and not fd.directory in (/etc/cassandra, /etc/ssl/certs/java)
- rule: Write below etc
- rule: write_etc
desc: an attempt to write to any file below /etc, not in a pipe installer session
condition: write_etc_common and not proc.sname=fbash
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
priority: WARNING
# Within a fbash session, the severity is lowered to INFO
- rule: Write below etc in installer
- rule: write_etc_installer
desc: an attempt to write to any file below /etc, in a pipe installer session
condition: write_etc_common and proc.sname=fbash
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name) within pipe installer session"
priority: INFO
- rule: Read sensitive file trusted after startup
- rule: read_sensitive_file_untrusted
desc: an attempt to read any sensitive file (e.g. files containing user/password/authentication information). Exceptions are made for known trusted programs.
condition: sensitive_files and open_read and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries, cron_binaries, iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash, sshd) and not proc.cmdline contains /usr/bin/mandb
output: "Sensitive file opened for reading by non-trusted program (user=%user.name command=%proc.cmdline file=%fd.name)"
priority: WARNING
- rule: read_sensitive_file_trusted_after_startup
desc: an attempt to read any sensitive file (e.g. files containing user/password/authentication information) by a trusted program after startup. Trusted programs might read these files at startup to load initial state, but not afterwards.
condition: sensitive_files and open_read and server_procs and not proc_is_new and proc.name!="sshd"
output: "Sensitive file opened for reading by trusted program after startup (user=%user.name command=%proc.cmdline file=%fd.name)"
priority: WARNING
- rule: Read sensitive file untrusted
desc: an attempt to read any sensitive file (e.g. files containing user/password/authentication information). Exceptions are made for known trusted programs.
condition: sensitive_files and open_read and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries, cron_binaries, iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, shell_binaries, sshd) and not proc.cmdline contains /usr/bin/mandb
output: "Sensitive file opened for reading by non-trusted program (user=%user.name name=%proc.name command=%proc.cmdline file=%fd.name)"
priority: WARNING
# Only let rpm-related programs write to the rpm database
- rule: Write below rpm database
- rule: write_rpm_database
desc: an attempt to write to the rpm database by any non-rpm related program
condition: fd.name startswith /var/lib/rpm and open_write and not proc.name in (rpm,rpmkey,yum)
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name)"
priority: WARNING
- rule: DB program spawned process
- rule: db_program_spawned_process
desc: a database-server related program spawned a new process other than itself. This shouldn\'t occur and is a follow on from some SQL injection attacks.
condition: proc.pname in (db_server_binaries) and spawned_process and not proc.name in (db_server_binaries)
output: "Database-related program spawned process other than itself (user=%user.name program=%proc.cmdline parent=%proc.pname)"
priority: WARNING
- rule: Modify binary dirs
- rule: modify_binary_dirs
desc: an attempt to modify any file below a set of binary directories.
condition: bin_dir_rename and modify and not package_mgmt_procs
output: "File below known binary directory renamed/removed (user=%user.name command=%proc.cmdline operation=%evt.type file=%fd.name %evt.args)"
priority: WARNING
- rule: Mkdir binary dirs
- rule: mkdir_binary_dirs
desc: an attempt to create a directory below a set of binary directories.
condition: mkdir and bin_dir_mkdir and not package_mgmt_procs
output: "Directory below known binary directory created (user=%user.name command=%proc.cmdline directory=%evt.arg.path)"
@@ -256,61 +250,43 @@
# priority: WARNING
# Temporarily disabling this rule as it's tripping over https://github.com/draios/sysdig/issues/598
# - rule: Syscall returns eaccess
# - rule: syscall_returns_eaccess
# desc: any system call that returns EACCESS. This is not always a strong indication of a problem, hence the INFO priority.
# condition: evt.res = EACCESS
# output: "System call returned EACCESS (user=%user.name command=%proc.cmdline syscall=%evt.type args=%evt.args)"
# priority: INFO
- rule: Change thread namespace
- rule: change_thread_namespace
desc: an attempt to change a program/thread\'s namespace (commonly done as a part of creating a container) by calling setns.
condition: evt.type = setns and not proc.name in (docker_binaries, sysdig, dragent, nsenter)
output: "Namespace change (setns) by unexpected program (user=%user.name command=%proc.cmdline container=%container.name (id=%container.id))"
condition: evt.type = setns and not proc.name in (docker, sysdig, dragent, nsenter, exe)
output: "Namespace change (setns) by unexpected program (user=%user.name command=%proc.cmdline container=%container.id)"
priority: WARNING
- rule: Run shell untrusted
- rule: run_shell_untrusted
desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries.
condition: spawned_process and not container and shell_procs and proc.pname exists and not proc.pname in (cron_binaries, shell_binaries, sshd, sudo, docker_binaries, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent, aws, initdb, docker-compose, make, configure, awk, falco)
condition: spawned_process and not container and proc.name = bash and proc.pname exists and not proc.pname in (cron_binaries, bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent)
output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)"
priority: WARNING
- macro: trusted_containers
condition: (container.image startswith sysdig/agent or container.image startswith sysdig/falco or container.image startswith sysdig/sysdig or container.image startswith gcr.io/google_containers/hyperkube)
- rule: File Open by Privileged Container
desc: Any open by a privileged container. Exceptions are made for known trusted images.
condition: (open_read or open_write) and container and container.privileged=true and not trusted_containers
output: File opened for read/write by non-privileged container (user=%user.name command=%proc.cmdline container=%container.name (id=%container.id) file=%fd.name)
priority: WARNING
- macro: sensitive_mount
condition: (container.mount.dest[/proc*] != "N/A")
- rule: Sensitive Mount by Container
desc: Any open by a container that has a mount from a sensitive host directory (i.e. /proc). Exceptions are made for known trusted images.
condition: (open_read or open_write) and container and sensitive_mount and not trusted_containers
output: File opened for read/write by container mounting sensitive directory (user=%user.name command=%proc.cmdline container=%container.name (id=%container.id) file=%fd.name)
priority: WARNING
# Anything run interactively by root
# - condition: evt.type != switch and user.name = root and proc.name != sshd and interactive
# output: "Interactive root (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
# priority: WARNING
- rule: System user interactive
- rule: system_user_interactive
desc: an attempt to run interactive commands by a system (i.e. non-login) user
condition: spawned_process and system_users and interactive
output: "System user ran an interactive command (user=%user.name command=%proc.cmdline)"
priority: WARNING
- rule: Run shell in container
- rule: run_shell_in_container
desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded.
condition: spawned_process and container and shell_procs and proc.pname exists and not proc.pname in (shell_binaries, docker_binaries, initdb, pg_ctl, awk, apache2)
condition: spawned_process and container and proc.name = bash and proc.pname exists and not proc.pname in (sh, bash, docker)
output: "Shell spawned in a container other than entrypoint (user=%user.name container_id=%container.id container_name=%container.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)"
priority: WARNING
# sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets
- rule: System procs network activity
- rule: system_procs_network_activity
desc: any network activity performed by system binaries that are not expected to send or receive any network traffic
condition: (fd.sockfamily = ip and system_procs) and (inbound or outbound)
output: "Known system binary sent/received network traffic (user=%user.name command=%proc.cmdline connection=%fd.name)"
@@ -319,46 +295,46 @@
# With the current restriction on system calls handled by falco
# (e.g. excluding read/write/sendto/recvfrom/etc, this rule won't
# trigger).
# - rule: Ssh error in syslog
# - rule: ssh_error_syslog
# desc: any ssh errors (failed logins, disconnects, ...) sent to syslog
# condition: syslog and ssh_error_message and evt.dir = <
# output: "sshd sent error message to syslog (error=%evt.buffer)"
# priority: WARNING
# sshd, mail programs attempt to setuid to root even when running as non-root. Excluding here to avoid meaningless FPs
- rule: Non sudo setuid
- rule: non_sudo_setuid
desc: an attempt to change users by calling setuid. sudo/su are excluded. user "root" is also excluded, as setuid calls typically involve dropping privileges.
condition: evt.type=setuid and evt.dir=> and not user.name=root and not proc.name in (userexec_binaries, mail_binaries, sshd, dbus-daemon-lau)
condition: evt.type=setuid and evt.dir=> and not user.name=root and not proc.name in (userexec_binaries, mail_binaries, sshd)
output: "Unexpected setuid call by non-sudo, non-root program (user=%user.name command=%proc.cmdline uid=%evt.arg.uid)"
priority: WARNING
- rule: User mgmt binaries
- rule: user_mgmt_binaries
desc: activity by any programs that can manage users, passwords, or permissions. sudo and su are excluded. Activity in containers is also excluded--some containers create custom users on top of a base linux distribution at startup.
condition: spawned_process and proc.name in (user_mgmt_binaries) and not proc.name in (su, sudo) and not container and not proc.pname in (cron_binaries, systemd, run-parts)
output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline parent=%proc.pname)"
priority: WARNING
# (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153)
- rule: Create files below dev
- rule: create_files_below_dev
desc: creating any files below /dev other than known programs that manage devices. Some rootkits hide files in /dev.
condition: fd.directory = /dev and (evt.type = creat or (evt.type = open and evt.arg.flags contains O_CREAT)) and proc.name != blkid and not fd.name in (/dev/null,/dev/stdin,/dev/stdout,/dev/stderr,/dev/tty)
output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)"
priority: WARNING
# fbash is a small shell script that runs bash, and is suitable for use in curl <curl> | fbash installers.
- rule: Installer bash starts network server
- rule: installer_bash_starts_network_server
desc: an attempt by a program in a pipe installer session to start listening for network connections
condition: evt.type=listen and proc.sname=fbash
output: "Unexpected listen call by a process in a fbash session (command=%proc.cmdline)"
priority: WARNING
- rule: Installer bash starts session
- rule: installer_bash_starts_session
desc: an attempt by a program in a pipe installer session to start a new session
condition: evt.type=setsid and proc.sname=fbash
output: "Unexpected setsid call by a process in fbash session (command=%proc.cmdline)"
priority: WARNING
- rule: Installer bash non https connection
- rule: installer_bash_non_https_connection
desc: an attempt by a program in a pipe installer session to make an outgoing connection on a non-http(s) port
condition: proc.sname=fbash and outbound and not fd.sport in (80, 443, 53)
output: "Outbound connection on non-http(s) port by a process in a fbash session (command=%proc.cmdline connection=%fd.name)"
@@ -371,7 +347,7 @@
# Notice when processes try to run chkconfig/systemctl.... to install a service.
# Note: this is not a WARNING, as you'd expect some service management
# as a part of doing the installation.
- rule: Installer bash manages service
- rule: installer_bash_manages_service
desc: an attempt by a program in a pipe installer session to manage a system service (systemd/chkconfig)
condition: evt.type=execve and proc.name in (chkconfig, systemctl) and proc.sname=fbash
output: "Service management program run by process in a fbash session (command=%proc.cmdline)"
@@ -380,7 +356,7 @@
# Notice when processes try to run any package management binary within a fbash session.
# Note: this is not a WARNING, as you'd expect some package management
# as a part of doing the installation
- rule: Installer bash runs pkgmgmt program
- rule: installer_bash_runs_pkgmgmt
desc: an attempt by a program in a pipe installer session to run a package management binary
condition: evt.type=execve and package_mgmt_procs and proc.sname=fbash
output: "Package management program run by process in a fbash session (command=%proc.cmdline)"
@@ -405,13 +381,13 @@
- macro: elasticsearch_port
condition: elasticsearch_cluster_port or elasticsearch_api_port
# - rule: Elasticsearch unexpected network inbound traffic
# - rule: elasticsearch_unexpected_network_inbound
# desc: inbound network traffic to elasticsearch on a port other than the standard ports
# condition: user.name = elasticsearch and inbound and not elasticsearch_port
# output: "Inbound network traffic to Elasticsearch on unexpected port (connection=%fd.name)"
# priority: WARNING
# - rule: Elasticsearch unexpected network outbound traffic
# - rule: elasticsearch_unexpected_network_outbound
# desc: outbound network traffic from elasticsearch on a port other than the standard ports
# condition: user.name = elasticsearch and outbound and not elasticsearch_cluster_port
# output: "Outbound network traffic from Elasticsearch on unexpected port (connection=%fd.name)"
@@ -426,13 +402,13 @@
- macro: activemq_port
condition: activemq_web_port or activemq_cluster_port
# - rule: Activemq unexpected network inbound traffic
# - rule: activemq_unexpected_network_inbound
# desc: inbound network traffic to activemq on a port other than the standard ports
# condition: user.name = activemq and inbound and not activemq_port
# output: "Inbound network traffic to ActiveMQ on unexpected port (connection=%fd.name)"
# priority: WARNING
# - rule: Activemq unexpected network outbound traffic
# - rule: activemq_unexpected_network_outbound
# desc: outbound network traffic from activemq on a port other than the standard ports
# condition: user.name = activemq and outbound and not activemq_cluster_port
# output: "Outbound network traffic from ActiveMQ on unexpected port (connection=%fd.name)"
@@ -454,13 +430,13 @@
- macro: cassandra_port
condition: cassandra_thrift_client_port or cassandra_cql_port or cassandra_cluster_port or cassandra_ssl_cluster_port or cassandra_jmx_port
# - rule: Cassandra unexpected network inbound traffic
# - rule: cassandra_unexpected_network_inbound
# desc: inbound network traffic to cassandra on a port other than the standard ports
# condition: user.name = cassandra and inbound and not cassandra_port
# output: "Inbound network traffic to Cassandra on unexpected port (connection=%fd.name)"
# priority: WARNING
# - rule: Cassandra unexpected network outbound traffic
# - rule: cassandra_unexpected_network_outbound
# desc: outbound network traffic from cassandra on a port other than the standard ports
# condition: user.name = cassandra and outbound and not (cassandra_ssl_cluster_port or cassandra_cluster_port)
# output: "Outbound network traffic from Cassandra on unexpected port (connection=%fd.name)"
@@ -481,13 +457,13 @@
- macro: fluentd_forward_port
condition: fd.sport=24224
# - rule: Fluentd unexpected network inbound traffic
# - rule: fluentd_unexpected_network_inbound
# desc: inbound network traffic to fluentd on a port other than the standard ports
# condition: user.name = td-agent and inbound and not (fluentd_forward_port or fluentd_http_port)
# output: "Inbound network traffic to Fluentd on unexpected port (connection=%fd.name)"
# priority: WARNING
# - rule: Tdagent unexpected network outbound traffic
# - rule: tdagent_unexpected_network_outbound
# desc: outbound network traffic from fluentd on a port other than the standard ports
# condition: user.name = td-agent and outbound and not fluentd_forward_port
# output: "Outbound network traffic from Fluentd on unexpected port (connection=%fd.name)"
@@ -495,7 +471,7 @@
# Gearman ports
# http://gearman.org/protocol/
# - rule: Gearman unexpected network outbound traffic
# - rule: gearman_unexpected_network_outbound
# desc: outbound network traffic from gearman on a port other than the standard ports
# condition: user.name = gearman and outbound and outbound and not fd.sport = 4730
# output: "Outbound network traffic from Gearman on unexpected port (connection=%fd.name)"
@@ -506,20 +482,20 @@
condition: fd.sport = 2181
# Kafka ports
# - rule: Kafka unexpected network inbound traffic
# - rule: kafka_unexpected_network_inbound
# desc: inbound network traffic to kafka on a port other than the standard ports
# condition: user.name = kafka and inbound and fd.sport != 9092
# output: "Inbound network traffic to Kafka on unexpected port (connection=%fd.name)"
# priority: WARNING
# Memcached ports
# - rule: Memcached unexpected network inbound traffic
# - rule: memcached_unexpected_network_inbound
# desc: inbound network traffic to memcached on a port other than the standard ports
# condition: user.name = memcached and inbound and fd.sport != 11211
# output: "Inbound network traffic to Memcached on unexpected port (connection=%fd.name)"
# priority: WARNING
# - rule: Memcached unexpected network outbound traffic
# - rule: memcached_network_outbound
# desc: any outbound network traffic from memcached. memcached never initiates outbound connections.
# condition: user.name = memcached and outbound
# output: "Unexpected Memcached outbound connection (connection=%fd.name)"
@@ -536,20 +512,20 @@
- macro: mongodb_webserver_port
condition: fd.sport = 28017
# - rule: Mongodb unexpected network inbound traffic
# - rule: mongodb_unexpected_network_inbound
# desc: inbound network traffic to mongodb on a port other than the standard ports
# condition: user.name = mongodb and inbound and not (mongodb_server_port or mongodb_shardserver_port or mongodb_configserver_port or mongodb_webserver_port)
# output: "Inbound network traffic to MongoDB on unexpected port (connection=%fd.name)"
# priority: WARNING
# MySQL ports
# - rule: Mysql unexpected network inbound traffic
# - rule: mysql_unexpected_network_inbound
# desc: inbound network traffic to mysql on a port other than the standard ports
# condition: user.name = mysql and inbound and fd.sport != 3306
# output: "Inbound network traffic to MySQL on unexpected port (connection=%fd.name)"
# priority: WARNING
# - rule: HTTP server unexpected network inbound traffic
# - rule: http_server_unexpected_network_inbound
# desc: inbound network traffic to a http server program on a port other than the standard ports
# condition: proc.name in (http_server_binaries) and inbound and fd.sport != 80 and fd.sport != 443
# output: "Inbound network traffic to HTTP Server on unexpected port (connection=%fd.name)"

View File

@@ -1,29 +1,17 @@
#!/bin/bash
#!/bin/sh
set -ex
PREFIX=$1
if [ -z $PREFIX ]; then
PREFIX=.
fi
mkdir -p $PREFIX
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcap.c -o $PREFIX/lpcap.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcode.c -o $PREFIX/lpcode.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpprint.c -o $PREFIX/lpprint.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lptree.c -o $PREFIX/lptree.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpvm.c -o $PREFIX/lpvm.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcap.c -o lpcap.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcode.c -o lpcode.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpprint.c -o lpprint.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lptree.c -o lptree.o
gcc -O2 -fPIC -I$LUA_INCLUDE -c lpvm.c -o lpvm.o
# For building lpeg.so, which we don't need now that we're statically linking lpeg.a into falco
#gcc -shared -o lpeg.so -L/usr/local/lib lpcap.o lpcode.o lpprint.o lptree.o lpvm.o
#gcc -shared -o lpeg.so -L/usr/local/lib lpcap.o lpcode.o lpprint.o lptree.o lpvm.o
pushd $PREFIX
/usr/bin/ar cr lpeg.a lpcap.o lpcode.o lpprint.o lptree.o lpvm.o
/usr/bin/ranlib lpeg.a
popd
chmod ug+w re.lua

View File

@@ -1,27 +0,0 @@
# File containing Falco rules, loaded at startup.
rules_file: /etc/falco_rules.yaml
# Whether to output events in json or text
json_output: false
# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: false
log_syslog: false
# Where security notifications should go.
# Multiple outputs can be enabled.
syslog_output:
enabled: false
file_output:
enabled: true
filename: /tmp/falco_outputs/file_output.txt
stdout_output:
enabled: true
program_output:
enabled: false
program: mail -s "Falco Notification" someone@example.com

View File

@@ -1,27 +0,0 @@
# File containing Falco rules, loaded at startup.
rules_file: /etc/falco_rules.yaml
# Whether to output events in json or text
json_output: false
# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: false
log_syslog: false
# Where security notifications should go.
# Multiple outputs can be enabled.
syslog_output:
enabled: false
file_output:
enabled: false
filename: ./output.txt
stdout_output:
enabled: true
program_output:
enabled: true
program: cat > /tmp/falco_outputs/program_output.txt

View File

@@ -26,32 +26,8 @@ class FalcoTest(Test):
self.json_output = self.params.get('json_output', '*', default=False)
self.rules_file = self.params.get('rules_file', '*', default=os.path.join(self.basedir, '../rules/falco_rules.yaml'))
if not isinstance(self.rules_file, list):
self.rules_file = [self.rules_file]
self.rules_args = ""
for file in self.rules_file:
if not os.path.isabs(file):
file = os.path.join(self.basedir, file)
self.rules_args = self.rules_args + "-r " + file + " "
self.conf_file = self.params.get('conf_file', '*', default=os.path.join(self.basedir, '../falco.yaml'))
if not os.path.isabs(self.conf_file):
self.conf_file = os.path.join(self.basedir, self.conf_file)
self.disabled_rules = self.params.get('disabled_rules', '*', default='')
if self.disabled_rules == '':
self.disabled_rules = []
if not isinstance(self.disabled_rules, list):
self.disabled_rules = [self.disabled_rules]
self.disabled_args = ""
for rule in self.disabled_rules:
self.disabled_args = self.disabled_args + "-D " + rule + " "
if not os.path.isabs(self.rules_file):
self.rules_file = os.path.join(self.basedir, self.rules_file)
self.rules_warning = self.params.get('rules_warning', '*', default=False)
if self.rules_warning == False:
@@ -73,9 +49,6 @@ class FalcoTest(Test):
if self.should_detect:
self.detect_level = self.params.get('detect_level', '*')
if not isinstance(self.detect_level, list):
self.detect_level = [self.detect_level]
# Doing this in 2 steps instead of simply using
# module_is_loaded to avoid logging lsmod output to the log.
lsmod_output = process.system_output("lsmod", verbose=False)
@@ -86,20 +59,6 @@ class FalcoTest(Test):
self.str_variant = self.trace_file
self.outputs = self.params.get('outputs', '*', default='')
if self.outputs == '':
self.outputs = {}
else:
outputs = []
for item in self.outputs:
for item2 in item:
output = {}
output['file'] = item2[0]
output['line'] = item2[1]
outputs.append(output)
self.outputs = outputs
def check_rules_warnings(self, res):
found_warning = sets.Set()
@@ -146,34 +105,16 @@ class FalcoTest(Test):
if events_detected == 0:
self.fail("Detected {} events when should have detected > 0".format(events_detected))
for level in self.detect_level:
level_line = '(?i){}: (\d+)'.format(level)
match = re.search(level_line, res.stdout)
level_line = '{}: (\d+)'.format(self.detect_level)
match = re.search(level_line, res.stdout)
if match is None:
self.fail("Could not find a line '{}: <count>' in falco output".format(level))
if match is None:
self.fail("Could not find a line '{}: <count>' in falco output".format(self.detect_level))
events_detected = int(match.group(1))
events_detected = int(match.group(1))
if not events_detected > 0:
self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, level))
def check_outputs(self):
for output in self.outputs:
# Open the provided file and match each line against the
# regex in line.
file = open(output['file'], 'r')
found = False
for line in file:
match = re.search(output['line'], line)
if match is not None:
found = True
if found == False:
self.fail("Could not find a line '{}' in file '{}'".format(output['line'], output['file']))
return True
if not events_detected > 0:
self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, self.detect_level))
def check_json_output(self, res):
if self.json_output:
@@ -190,8 +131,8 @@ class FalcoTest(Test):
self.log.info("Trace file %s", self.trace_file)
# Run the provided trace file though falco
cmd = '{}/userspace/falco/falco {} {} -c {} -e {} -o json_output={} -v'.format(
self.falcodir, self.rules_args, self.disabled_args, self.conf_file, self.trace_file, self.json_output)
cmd = '{}/userspace/falco/falco -r {} -c {}/../falco.yaml -e {} -o json_output={} -v'.format(
self.falcodir, self.rules_file, self.falcodir, self.trace_file, self.json_output)
self.falco_proc = process.SubProcess(cmd)
@@ -206,7 +147,6 @@ class FalcoTest(Test):
self.check_rules_events(res)
self.check_detections(res)
self.check_json_output(res)
self.check_outputs()
pass

View File

@@ -1,13 +1,13 @@
trace_files: !mux
builtin_rules_no_warnings:
detect: False
trace_file: trace_files/empty.scap
trace_file: empty.scap
rules_warning: False
test_warnings:
detect: False
trace_file: trace_files/empty.scap
rules_file: rules/falco_rules_warnings.yaml
trace_file: empty.scap
rules_file: falco_rules_warnings.yaml
rules_warning:
- no_evttype
- evttype_not_equals
@@ -60,81 +60,3 @@ trace_files: !mux
- repeated_evttypes_with_in: [open]
- repeated_evttypes_with_separate_in: [open]
- repeated_evttypes_with_mix: [open]
rule_names_with_spaces:
detect: True
detect_level: WARNING
rules_file:
- rules/rule_names_with_spaces.yaml
trace_file: trace_files/cat_write.scap
multiple_rules_first_empty:
detect: True
detect_level: WARNING
rules_file:
- rules/empty_rules.yaml
- rules/single_rule.yaml
trace_file: trace_files/cat_write.scap
multiple_rules_last_empty:
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule.yaml
- rules/empty_rules.yaml
trace_file: trace_files/cat_write.scap
multiple_rules:
detect: True
detect_level:
- WARNING
- INFO
- ERROR
rules_file:
- rules/single_rule.yaml
- rules/double_rule.yaml
trace_file: trace_files/cat_write.scap
disabled_rules:
detect: False
rules_file:
- rules/empty_rules.yaml
- rules/single_rule.yaml
disabled_rules:
- open_from_cat
trace_file: trace_files/cat_write.scap
disabled_rules_using_regex:
detect: False
rules_file:
- rules/empty_rules.yaml
- rules/single_rule.yaml
disabled_rules:
- "open.*"
trace_file: trace_files/cat_write.scap
disabled_rules_using_enabled_flag:
detect: False
rules_file:
- rules/single_rule_enabled_flag.yaml
trace_file: trace_files/cat_write.scap
file_output:
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule.yaml
conf_file: confs/file_output.yaml
trace_file: trace_files/cat_write.scap
outputs:
- /tmp/falco_outputs/file_output.txt: Warning An open was seen
program_output:
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule.yaml
conf_file: confs/program_output.yaml
trace_file: trace_files/cat_write.scap
outputs:
- /tmp/falco_outputs/program_output.txt: Warning An open was seen

View File

@@ -1,13 +0,0 @@
# 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

View File

@@ -1,8 +0,0 @@
- macro: is_cat
condition: proc.name=cat
- 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

View File

@@ -1,8 +0,0 @@
- macro: is_cat
condition: proc.name=cat
- 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

View File

@@ -1,9 +0,0 @@
- macro: is_cat
condition: proc.name=cat
- 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
enabled: false

View File

@@ -38,20 +38,18 @@ EOF
function prepare_multiplex_file() {
cp $SCRIPTDIR/falco_tests.yaml.in $MULT_FILE
prepare_multiplex_fileset traces-positive True WARNING False
prepare_multiplex_fileset traces-negative False WARNING True
prepare_multiplex_fileset traces-info True INFO False
prepare_multiplex_fileset traces-positive True Warning False
prepare_multiplex_fileset traces-negative False Warning True
prepare_multiplex_fileset traces-info True Informational False
prepare_multiplex_fileset traces-positive True WARNING True
prepare_multiplex_fileset traces-info True INFO True
prepare_multiplex_fileset traces-positive True Warning True
prepare_multiplex_fileset traces-info True Informational True
echo "Contents of $MULT_FILE:"
cat $MULT_FILE
}
function run_tests() {
rm -rf /tmp/falco_outputs
mkdir /tmp/falco_outputs
CMD="avocado run --multiplex $MULT_FILE --job-results-dir $SCRIPTDIR/job-results -- $SCRIPTDIR/falco_test.py"
echo "Running: $CMD"
$CMD

Binary file not shown.

View File

@@ -1,31 +0,0 @@
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp/third-party/jsoncpp")
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libscap")
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp")
include_directories("${PROJECT_BINARY_DIR}/userspace/engine")
include_directories("${LUAJIT_INCLUDE}")
add_library(falco_engine STATIC rules.cpp falco_common.cpp falco_engine.cpp)
target_include_directories(falco_engine PUBLIC
"${LUAJIT_INCLUDE}")
target_link_libraries(falco_engine
"${FALCO_SINSP_LIBRARY}"
"${LPEG_LIB}"
"${LYAML_LIB}"
"${LIBYAML_LIB}")
configure_file(config_falco_engine.h.in config_falco_engine.h)
if(DEFINED FALCO_COMPONENT)
install(DIRECTORY lua
DESTINATION "${FALCO_SHARE_DIR}"
COMPONENT "${FALCO_COMPONENT}"
FILES_MATCHING PATTERN *.lua)
else()
install(DIRECTORY lua
DESTINATION "${FALCO_SHARE_DIR}"
FILES_MATCHING PATTERN *.lua)
endif()
add_subdirectory("${PROJECT_SOURCE_DIR}/../falco/rules" "${PROJECT_BINARY_DIR}/rules")

View File

@@ -1,22 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#define FALCO_ENGINE_LUA_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}/lua/"
#define FALCO_ENGINE_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/../falco/userspace/engine/lua/"

View File

@@ -1,108 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fstream>
#include "config_falco_engine.h"
#include "falco_common.h"
falco_common::falco_common()
{
m_ls = lua_open();
luaL_openlibs(m_ls);
}
falco_common::~falco_common()
{
if(m_ls)
{
lua_close(m_ls);
}
}
void falco_common::set_inspector(sinsp *inspector)
{
m_inspector = inspector;
}
void falco_common::init(const char *lua_main_filename, const char *source_dir)
{
ifstream is;
string lua_dir = FALCO_ENGINE_LUA_DIR;
string lua_main_path = lua_dir + lua_main_filename;
is.open(lua_main_path);
if (!is.is_open())
{
lua_dir = source_dir;
lua_main_path = lua_dir + lua_main_filename;
is.open(lua_main_path);
if (!is.is_open())
{
throw falco_exception("Could not find Falco Lua entrypoint (tried " +
string(FALCO_ENGINE_LUA_DIR) + lua_main_filename + ", " +
string(source_dir) + lua_main_filename + ")");
}
}
// Initialize Lua interpreter
add_lua_path(lua_dir);
// Load the main program, which defines all the available functions.
string scriptstr((istreambuf_iterator<char>(is)),
istreambuf_iterator<char>());
if(luaL_loadstring(m_ls, scriptstr.c_str()) || lua_pcall(m_ls, 0, 0, 0))
{
throw falco_exception("Failed to load script " +
lua_main_path + ": " + lua_tostring(m_ls, -1));
}
}
void falco_common::add_lua_path(string &path)
{
string cpath = string(path);
path += "?.lua";
cpath += "?.so";
lua_getglobal(m_ls, "package");
lua_getfield(m_ls, -1, "path");
string cur_path = lua_tostring(m_ls, -1 );
cur_path += ';';
lua_pop(m_ls, 1);
cur_path.append(path.c_str());
lua_pushstring(m_ls, cur_path.c_str());
lua_setfield(m_ls, -2, "path");
lua_getfield(m_ls, -1, "cpath");
string cur_cpath = lua_tostring(m_ls, -1 );
cur_cpath += ';';
lua_pop(m_ls, 1);
cur_cpath.append(cpath.c_str());
lua_pushstring(m_ls, cur_cpath.c_str());
lua_setfield(m_ls, -2, "cpath");
lua_pop(m_ls, 1);
}

View File

@@ -1,87 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <exception>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include <sinsp.h>
//
// Most falco_* classes can throw exceptions. Unless directly related
// to low-level failures like inability to open file, etc, they will
// be of this type.
//
struct falco_exception : std::exception
{
falco_exception()
{
}
virtual ~falco_exception() throw()
{
}
falco_exception(std::string error_str)
{
m_error_str = error_str;
}
char const* what() const throw()
{
return m_error_str.c_str();
}
std::string m_error_str;
};
//
// This is the base class of falco_engine/falco_output. It is
// responsible for managing a lua state and associated inspector and
// loading a single "main" lua file into that state.
//
class falco_common
{
public:
falco_common();
virtual ~falco_common();
void init(const char *lua_main_filename, const char *source_dir);
void set_inspector(sinsp *inspector);
protected:
lua_State *m_ls;
sinsp *m_inspector;
private:
void add_lua_path(std::string &path);
};

View File

@@ -1,201 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <unistd.h>
#include <string>
#include <fstream>
#include "falco_engine.h"
#include "config_falco_engine.h"
extern "C" {
#include "lpeg.h"
#include "lyaml.h"
}
#include "utils.h"
string lua_on_event = "on_event";
string lua_print_stats = "print_stats";
using namespace std;
falco_engine::falco_engine(bool seed_rng)
: m_rules(NULL), m_sampling_ratio(1), m_sampling_multiplier(0)
{
luaopen_lpeg(m_ls);
luaopen_yaml(m_ls);
falco_common::init(m_lua_main_filename.c_str(), FALCO_ENGINE_SOURCE_LUA_DIR);
falco_rules::init(m_ls);
if(seed_rng)
{
srandom((unsigned) getpid());
}
}
falco_engine::~falco_engine()
{
if (m_rules)
{
delete m_rules;
}
}
void falco_engine::load_rules(const string &rules_content, bool verbose, bool all_events)
{
// The engine must have been given an inspector by now.
if(! m_inspector)
{
throw falco_exception("No inspector provided");
}
if(!m_rules)
{
m_rules = new falco_rules(m_inspector, this, m_ls);
}
m_rules->load_rules(rules_content, verbose, all_events);
}
void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events)
{
ifstream is;
is.open(rules_filename);
if (!is.is_open())
{
throw falco_exception("Could not open rules filename " +
rules_filename + " " +
"for reading");
}
string rules_content((istreambuf_iterator<char>(is)),
istreambuf_iterator<char>());
load_rules(rules_content, verbose, all_events);
}
void falco_engine::enable_rule(string &pattern, bool enabled)
{
m_evttype_filter.enable(pattern, enabled);
}
falco_engine::rule_result *falco_engine::process_event(sinsp_evt *ev)
{
if(should_drop_evt())
{
return NULL;
}
if(!m_evttype_filter.run(ev))
{
return NULL;
}
struct rule_result *res = new rule_result();
lua_getglobal(m_ls, lua_on_event.c_str());
if(lua_isfunction(m_ls, -1))
{
lua_pushlightuserdata(m_ls, ev);
lua_pushnumber(m_ls, ev->get_check_id());
if(lua_pcall(m_ls, 2, 3, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function output: " + string(lerr);
throw falco_exception(err);
}
res->evt = ev;
const char *p = lua_tostring(m_ls, -3);
res->rule = p;
res->priority = lua_tostring(m_ls, -2);
res->format = lua_tostring(m_ls, -1);
lua_pop(m_ls, 3);
}
else
{
throw falco_exception("No function " + lua_on_event + " found in lua compiler module");
}
return res;
}
void falco_engine::describe_rule(string *rule)
{
return m_rules->describe_rule(rule);
}
// Print statistics on the the rules that triggered
void falco_engine::print_stats()
{
lua_getglobal(m_ls, lua_print_stats.c_str());
if(lua_isfunction(m_ls, -1))
{
if(lua_pcall(m_ls, 0, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function print_stats: " + string(lerr);
throw falco_exception(err);
}
}
else
{
throw falco_exception("No function " + lua_print_stats + " found in lua rule loader module");
}
}
void falco_engine::add_evttype_filter(string &rule,
list<uint32_t> &evttypes,
sinsp_filter* filter)
{
m_evttype_filter.add(rule, evttypes, filter);
}
void falco_engine::set_sampling_ratio(uint32_t sampling_ratio)
{
m_sampling_ratio = sampling_ratio;
}
void falco_engine::set_sampling_multiplier(double sampling_multiplier)
{
m_sampling_multiplier = sampling_multiplier;
}
inline bool falco_engine::should_drop_evt()
{
if(m_sampling_multiplier == 0)
{
return false;
}
if(m_sampling_ratio == 1)
{
return false;
}
double coin = (random() * (1.0/RAND_MAX));
return (coin >= (1.0/(m_sampling_multiplier * m_sampling_ratio)));
}

View File

@@ -1,136 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include "sinsp.h"
#include "filter.h"
#include "rules.h"
#include "falco_common.h"
//
// This class acts as the primary interface between a program and the
// falco rules engine. Falco outputs (writing to files/syslog/etc) are
// handled in a separate class falco_outputs.
//
class falco_engine : public falco_common
{
public:
falco_engine(bool seed_rng=true);
virtual ~falco_engine();
//
// Load rules either directly or from a filename.
//
void load_rules_file(const std::string &rules_filename, bool verbose, bool all_events);
void load_rules(const std::string &rules_content, bool verbose, bool all_events);
//
// Enable/Disable any rules matching the provided pattern (regex).
//
void enable_rule(std::string &pattern, bool enabled);
struct rule_result {
sinsp_evt *evt;
std::string rule;
std::string priority;
std::string format;
};
//
// Given an event, check it against the set of rules in the
// engine and if a matching rule is found, return details on
// the rule that matched. If no rule matched, returns NULL.
//
// the reutrned rule_result is allocated and must be delete()d.
rule_result *process_event(sinsp_evt *ev);
//
// Print details on the given rule. If rule is NULL, print
// details on all rules.
//
void describe_rule(std::string *rule);
//
// Print statistics on how many events matched each rule.
//
void print_stats();
//
// Add a filter, which is related to the specified list of
// event types, to the engine.
//
void add_evttype_filter(std::string &rule,
list<uint32_t> &evttypes,
sinsp_filter* filter);
//
// Set the sampling ratio, which can affect which events are
// matched against the set of rules.
//
void set_sampling_ratio(uint32_t sampling_ratio);
//
// Set the sampling ratio multiplier, which can affect which
// events are matched against the set of rules.
//
void set_sampling_multiplier(double sampling_multiplier);
private:
//
// Determine whether the given event should be matched at all
// against the set of rules, given the current sampling
// ratio/multiplier.
//
inline bool should_drop_evt();
falco_rules *m_rules;
sinsp_evttype_filter m_evttype_filter;
//
// Here's how the sampling ratio and multiplier influence
// whether or not an event is dropped in
// should_drop_evt(). The intent is that m_sampling_ratio is
// generally changing external to the engine e.g. in the main
// inspector class based on how busy the inspector is. A
// sampling ratio implies no dropping. Values > 1 imply
// increasing levels of dropping. External to the engine, the
// sampling ratio results in events being dropped at the
// kernel/inspector interface.
//
// The sampling multiplier is an amplification to the sampling
// factor in m_sampling_ratio. If 0, no additional events are
// dropped other than those that might be dropped by the
// kernel/inspector interface. If 1, events that make it past
// the kernel module are subject to an additional level of
// dropping at the falco engine, scaling with the sampling
// ratio in m_sampling_ratio.
//
uint32_t m_sampling_ratio;
double m_sampling_multiplier;
std::string m_lua_main_filename = "rule_loader.lua";
};

View File

@@ -1,55 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <list>
#include "sinsp.h"
#include "lua_parser.h"
class falco_engine;
class falco_rules
{
public:
falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls);
~falco_rules();
void load_rules(const string &rules_content, bool verbose, bool all_events);
void describe_rule(string *rule);
static void init(lua_State *ls);
static int add_filter(lua_State *ls);
static int enable_rule(lua_State *ls);
private:
void add_filter(string &rule, list<uint32_t> &evttypes);
void enable_rule(string &rule, bool enabled);
lua_parser* m_lua_parser;
sinsp* m_inspector;
falco_engine *m_engine;
lua_State* m_ls;
string m_lua_load_rules = "load_rules";
string m_lua_ignored_syscalls = "ignored_syscalls";
string m_lua_ignored_events = "ignored_events";
string m_lua_events = "events";
string m_lua_describe_rule = "describe_rule";
};

View File

@@ -3,20 +3,22 @@ include_directories("${LUAJIT_INCLUDE}")
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libscap")
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp")
include_directories("${PROJECT_SOURCE_DIR}/userspace/engine")
include_directories("${PROJECT_BINARY_DIR}/userspace/falco")
include_directories("${CURL_INCLUDE_DIR}")
include_directories("${YAMLCPP_INCLUDE_DIR}")
include_directories("${DRAIOS_DEPENDENCIES_DIR}/yaml-${DRAIOS_YAML_VERSION}/target/include")
add_executable(falco configuration.cpp formats.cpp logger.cpp falco_outputs.cpp falco.cpp)
add_executable(falco configuration.cpp formats.cpp fields.cpp rules.cpp logger.cpp falco.cpp)
target_link_libraries(falco falco_engine sinsp)
target_link_libraries(falco sinsp)
target_link_libraries(falco
"${LPEG_SRC}/lpeg.a"
"${LYAML_LIB}"
"${LIBYAML_LIB}"
"${YAMLCPP_LIB}")
set(FALCO_LUA_MAIN "rule_loader.lua")
configure_file(config_falco.h.in config_falco.h)
install(TARGETS falco DESTINATION bin)

View File

@@ -2,10 +2,13 @@
#define FALCO_VERSION "${FALCO_VERSION}"
#define FALCO_LUA_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}/lua/"
#define FALCO_LUA_DIR "/usr/share/falco/lua/"
#define FALCO_SOURCE_DIR "${PROJECT_SOURCE_DIR}"
#define FALCO_SOURCE_CONF_FILE "${PROJECT_SOURCE_DIR}/falco.yaml"
#define FALCO_INSTALL_CONF_FILE "/etc/falco.yaml"
#define FALCO_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/userspace/falco/lua/"
#define FALCO_LUA_MAIN "${FALCO_LUA_MAIN}"
#define PROBE_NAME "${PROBE_NAME}"

View File

@@ -1,60 +1,32 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include "configuration.h"
#include "config_falco.h"
#include "sinsp.h"
#include "logger.h"
using namespace std;
falco_configuration::falco_configuration()
: m_config(NULL)
{
}
falco_configuration::~falco_configuration()
{
if (m_config)
{
delete m_config;
}
}
// If we don't have a configuration file, we just use stdout output and all other defaults
void falco_configuration::init(list<string> &cmdline_options)
void falco_configuration::init(std::list<std::string> &cmdline_options)
{
init_cmdline_options(cmdline_options);
falco_outputs::output_config stdout_output;
output_config stdout_output;
stdout_output.name = "stdout";
m_outputs.push_back(stdout_output);
}
void falco_configuration::init(string conf_filename, list<string> &cmdline_options)
void falco_configuration::init(string conf_filename, std::list<std::string> &cmdline_options)
{
string m_config_file = conf_filename;
m_config = new yaml_configuration(m_config_file);
init_cmdline_options(cmdline_options);
m_rules_filenames.push_back(m_config->get_scalar<string>("rules_file", "/etc/falco_rules.yaml"));
m_rules_filename = m_config->get_scalar<string>("rules_file", "/etc/falco_rules.yaml");
m_json_output = m_config->get_scalar<bool>("json_output", false);
falco_outputs::output_config file_output;
output_config file_output;
file_output.name = "file";
if (m_config->get_scalar<bool>("file_output", "enabled", false))
{
@@ -62,27 +34,27 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
filename = m_config->get_scalar<string>("file_output", "filename", "");
if (filename == string(""))
{
throw invalid_argument("Error reading config file (" + m_config_file + "): file output enabled but no filename in configuration block");
throw sinsp_exception("Error reading config file (" + m_config_file + "): file output enabled but no filename in configuration block");
}
file_output.options["filename"] = filename;
m_outputs.push_back(file_output);
}
falco_outputs::output_config stdout_output;
output_config stdout_output;
stdout_output.name = "stdout";
if (m_config->get_scalar<bool>("stdout_output", "enabled", false))
{
m_outputs.push_back(stdout_output);
}
falco_outputs::output_config syslog_output;
output_config syslog_output;
syslog_output.name = "syslog";
if (m_config->get_scalar<bool>("syslog_output", "enabled", false))
{
m_outputs.push_back(syslog_output);
}
falco_outputs::output_config program_output;
output_config program_output;
program_output.name = "program";
if (m_config->get_scalar<bool>("program_output", "enabled", false))
{
@@ -98,7 +70,7 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
if (m_outputs.size() == 0)
{
throw invalid_argument("Error reading config file (" + m_config_file + "): No outputs configured. Please configure at least one output file output enabled but no filename in configuration block");
throw sinsp_exception("Error reading config file (" + m_config_file + "): No outputs configured. Please configure at least one output file output enabled but no filename in configuration block");
}
falco_logger::log_stderr = m_config->get_scalar<bool>("log_stderr", false);
@@ -118,7 +90,7 @@ static bool split(const string &str, char delim, pair<string,string> &parts)
return true;
}
void falco_configuration::init_cmdline_options(list<string> &cmdline_options)
void falco_configuration::init_cmdline_options(std::list<std::string> &cmdline_options)
{
for(const string &option : cmdline_options)
{
@@ -126,13 +98,13 @@ void falco_configuration::init_cmdline_options(list<string> &cmdline_options)
}
}
void falco_configuration::set_cmdline_option(const string &opt)
void falco_configuration::set_cmdline_option(const std::string &opt)
{
pair<string,string> keyval;
pair<string,string> subkey;
if (! split(opt, '=', keyval)) {
throw invalid_argument("Error parsing config option \"" + opt + "\". Must be of the form key=val or key.subkey=val");
throw sinsp_exception("Error parsing config option \"" + opt + "\". Must be of the form key=val or key.subkey=val");
}
if (split(keyval.first, '.', subkey)) {

View File

@@ -1,30 +1,13 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <yaml-cpp/yaml.h>
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include "falco_outputs.h"
struct output_config
{
std::string name;
std::map<std::string, std::string> options;
};
class yaml_configuration
{
@@ -34,7 +17,7 @@ public:
{
m_path = path;
YAML::Node config;
std::vector<falco_outputs::output_config> outputs;
std::vector<output_config> outputs;
try
{
m_root = YAML::LoadFile(path);
@@ -135,15 +118,12 @@ private:
class falco_configuration
{
public:
falco_configuration();
virtual ~falco_configuration();
void init(std::string conf_filename, std::list<std::string> &cmdline_options);
void init(std::list<std::string> &cmdline_options);
std::list<std::string> m_rules_filenames;
std::string m_rules_filename;
bool m_json_output;
std::vector<falco_outputs::output_config> m_outputs;
std::vector<output_config> m_outputs;
private:
void init_cmdline_options(std::list<std::string> &cmdline_options);

View File

@@ -1,41 +1,32 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#define __STDC_FORMAT_MACROS
#include <stdio.h>
#include <fstream>
#include <set>
#include <list>
#include <string>
#include <iostream>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <algorithm>
#include <unistd.h>
#include <getopt.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "lpeg.h"
#include "lyaml.h"
}
#include <sinsp.h>
#include "logger.h"
#include "configuration.h"
#include "falco_engine.h"
#include "config_falco.h"
#include "configuration.h"
#include "rules.h"
#include "formats.h"
#include "fields.h"
#include "logger.h"
#include "utils.h"
#include <yaml-cpp/yaml.h>
bool g_terminate = false;
//
@@ -62,8 +53,6 @@ static void usage()
" -p, --pidfile <pid_file> When run as a daemon, write pid to specified file\n"
" -e <events_file> Read the events from <events_file> (in .scap format) instead of tapping into live.\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"
" -D <pattern> Disable any rules matching the regex <pattern>. Can be specified multiple times.\n"
" -L Show the name and description of all rules and exit.\n"
" -l <rule> Show the name and description of the rule with name <rule> and exit.\n"
" -v Verbose output.\n"
@@ -72,7 +61,7 @@ static void usage()
);
}
static void display_fatal_err(const string &msg)
static void display_fatal_err(const string &msg, bool daemon)
{
falco_logger::log(LOG_ERR, msg);
@@ -86,18 +75,23 @@ static void display_fatal_err(const string &msg)
}
}
string lua_on_event = "on_event";
string lua_add_output = "add_output";
string lua_print_stats = "print_stats";
// Splitting into key=value or key.subkey=value will be handled by configuration class.
std::list<string> cmdline_options;
//
// Event processing loop
//
void do_inspect(falco_engine *engine,
falco_outputs *outputs,
sinsp* inspector)
void do_inspect(sinsp* inspector,
falco_rules* rules,
lua_State* ls)
{
int32_t res;
sinsp_evt* ev;
string line;
//
// Loop through the events
@@ -135,20 +129,112 @@ void do_inspect(falco_engine *engine,
continue;
}
// As the inspector has no filter at its level, all
// events are returned here. Pass them to the falco
// engine, which will match the event against the set
// of rules. If a match is found, pass the event to
// the outputs.
falco_engine::rule_result *res = engine->process_event(ev);
if(res)
lua_getglobal(ls, lua_on_event.c_str());
if(lua_isfunction(ls, -1))
{
outputs->handle_event(res->evt, res->rule, res->priority, res->format);
delete(res);
lua_pushlightuserdata(ls, ev);
lua_pushnumber(ls, ev->get_check_id());
if(lua_pcall(ls, 2, 0, 0) != 0)
{
const char* lerr = lua_tostring(ls, -1);
string err = "Error invoking function output: " + string(lerr);
throw sinsp_exception(err);
}
}
else
{
throw sinsp_exception("No function " + lua_on_event + " found in lua compiler module");
}
}
}
void add_lua_path(lua_State *ls, string path)
{
string cpath = string(path);
path += "?.lua";
cpath += "?.so";
lua_getglobal(ls, "package");
lua_getfield(ls, -1, "path");
string cur_path = lua_tostring(ls, -1 );
cur_path += ';';
lua_pop(ls, 1);
cur_path.append(path.c_str());
lua_pushstring(ls, cur_path.c_str());
lua_setfield(ls, -2, "path");
lua_getfield(ls, -1, "cpath");
string cur_cpath = lua_tostring(ls, -1 );
cur_cpath += ';';
lua_pop(ls, 1);
cur_cpath.append(cpath.c_str());
lua_pushstring(ls, cur_cpath.c_str());
lua_setfield(ls, -2, "cpath");
lua_pop(ls, 1);
}
void add_output(lua_State *ls, output_config oc)
{
uint8_t nargs = 1;
lua_getglobal(ls, lua_add_output.c_str());
if(!lua_isfunction(ls, -1))
{
throw sinsp_exception("No function " + lua_add_output + " found. ");
}
lua_pushstring(ls, oc.name.c_str());
// If we have options, build up a lua table containing them
if (oc.options.size())
{
nargs = 2;
lua_createtable(ls, 0, oc.options.size());
for (auto it = oc.options.cbegin(); it != oc.options.cend(); ++it)
{
lua_pushstring(ls, (*it).second.c_str());
lua_setfield(ls, -2, (*it).first.c_str());
}
}
if(lua_pcall(ls, nargs, 0, 0) != 0)
{
const char* lerr = lua_tostring(ls, -1);
throw sinsp_exception(string(lerr));
}
}
// Print statistics on the the rules that triggered
void print_stats(lua_State *ls)
{
lua_getglobal(ls, lua_print_stats.c_str());
if(lua_isfunction(ls, -1))
{
if(lua_pcall(ls, 0, 0, 0) != 0)
{
const char* lerr = lua_tostring(ls, -1);
string err = "Error invoking function print_stats: " + string(lerr);
throw sinsp_exception(err);
}
}
else
{
throw sinsp_exception("No function " + lua_print_stats + " found in lua rule loader module");
}
}
//
// ARGUMENT PARSING AND PROGRAM SETUP
//
@@ -156,13 +242,15 @@ int falco_init(int argc, char **argv)
{
int result = EXIT_SUCCESS;
sinsp* inspector = NULL;
falco_engine *engine = NULL;
falco_outputs *outputs = NULL;
falco_rules* rules = NULL;
int op;
int long_index = 0;
string lua_main_filename;
string scap_filename;
string conf_filename;
list<string> rules_filenames;
string rules_filename;
string lua_dir = FALCO_LUA_DIR;
lua_State* ls = NULL;
bool daemon = false;
string pidfilename = "/var/run/falco.pid";
bool describe_all_rules = false;
@@ -183,20 +271,12 @@ int falco_init(int argc, char **argv)
try
{
inspector = new sinsp();
engine = new falco_engine();
engine->set_inspector(inspector);
outputs = new falco_outputs();
outputs->set_inspector(inspector);
set<string> disabled_rule_patterns;
string pattern;
//
// Parse the args
//
while((op = getopt_long(argc, argv,
"c:ho:e:r:D:dp:Ll:vA",
"c:ho:e:r:dp:Ll:vA",
long_options, &long_index)) != -1)
{
switch(op)
@@ -214,11 +294,7 @@ int falco_init(int argc, char **argv)
scap_filename = optarg;
break;
case 'r':
rules_filenames.push_back(optarg);
break;
case 'D':
pattern = optarg;
disabled_rule_patterns.insert(pattern);
rules_filename = optarg;
break;
case 'd':
daemon = true;
@@ -249,29 +325,29 @@ int falco_init(int argc, char **argv)
// Some combinations of arguments are not allowed.
if (daemon && pidfilename == "") {
throw std::invalid_argument("If -d is provided, a pid file must also be provided");
throw sinsp_exception("If -d is provided, a pid file must also be provided");
}
ifstream conf_stream;
ifstream* conf_stream;
if (conf_filename.size())
{
conf_stream.open(conf_filename);
if (!conf_stream.is_open())
conf_stream = new ifstream(conf_filename);
if (!conf_stream->good())
{
throw std::runtime_error("Could not find configuration file at " + conf_filename);
throw sinsp_exception("Could not find configuration file at " + conf_filename);
}
}
else
{
conf_stream.open(FALCO_SOURCE_CONF_FILE);
if (!conf_stream.is_open())
conf_stream = new ifstream(FALCO_SOURCE_CONF_FILE);
if (conf_stream->good())
{
conf_filename = FALCO_SOURCE_CONF_FILE;
}
else
{
conf_stream.open(FALCO_INSTALL_CONF_FILE);
if (!conf_stream.is_open())
conf_stream = new ifstream(FALCO_INSTALL_CONF_FILE);
if (conf_stream->good())
{
conf_filename = FALCO_INSTALL_CONF_FILE;
}
@@ -295,47 +371,66 @@ int falco_init(int argc, char **argv)
falco_logger::log(LOG_INFO, "Falco initialized. No configuration file found, proceeding with defaults\n");
}
if (rules_filenames.size())
if (rules_filename.size())
{
config.m_rules_filenames = rules_filenames;
config.m_rules_filename = rules_filename;
}
for (auto filename : config.m_rules_filenames)
lua_main_filename = lua_dir + FALCO_LUA_MAIN;
if (!std::ifstream(lua_main_filename))
{
engine->load_rules_file(filename, verbose, all_events);
falco_logger::log(LOG_INFO, "Parsed rules from file " + filename + "\n");
lua_dir = FALCO_SOURCE_LUA_DIR;
lua_main_filename = lua_dir + FALCO_LUA_MAIN;
if (!std::ifstream(lua_main_filename))
{
falco_logger::log(LOG_ERR, "Could not find Falco Lua libraries (tried " +
string(FALCO_LUA_DIR FALCO_LUA_MAIN) + ", " +
lua_main_filename + "). Exiting.\n");
result = EXIT_FAILURE;
goto exit;
}
}
for (auto pattern : disabled_rule_patterns)
{
falco_logger::log(LOG_INFO, "Disabling rules matching pattern: " + pattern + "\n");
engine->enable_rule(pattern, false);
}
// Initialize Lua interpreter
ls = lua_open();
luaL_openlibs(ls);
luaopen_lpeg(ls);
luaopen_yaml(ls);
add_lua_path(ls, lua_dir);
rules = new falco_rules(inspector, ls, lua_main_filename);
falco_formats::init(inspector, ls, config.m_json_output);
falco_fields::init(inspector, ls);
falco_logger::init(ls);
falco_rules::init(ls);
outputs->init(config.m_json_output);
if(!all_events)
{
inspector->set_drop_event_flags(EF_DROP_FALCO);
}
rules->load_rules(config.m_rules_filename, verbose, all_events);
falco_logger::log(LOG_INFO, "Parsed rules from file " + config.m_rules_filename + "\n");
if (describe_all_rules)
{
engine->describe_rule(NULL);
rules->describe_rule(NULL);
goto exit;
}
if (describe_rule != "")
{
engine->describe_rule(&describe_rule);
rules->describe_rule(&describe_rule);
goto exit;
}
inspector->set_hostname_and_port_resolution_mode(false);
for(auto output : config.m_outputs)
for(std::vector<output_config>::iterator it = config.m_outputs.begin(); it != config.m_outputs.end(); ++it)
{
outputs->add_output(output);
add_output(ls, *it);
}
if(signal(SIGINT, signal_callback) == SIG_ERR)
@@ -427,17 +522,23 @@ int falco_init(int argc, char **argv)
open("/dev/null", O_RDWR);
}
do_inspect(engine,
outputs,
inspector);
do_inspect(inspector,
rules,
ls);
inspector->close();
engine->print_stats();
print_stats(ls);
}
catch(exception &e)
catch(sinsp_exception& e)
{
display_fatal_err("Runtime error: " + string(e.what()) + ". Exiting.\n");
display_fatal_err("Runtime error: " + string(e.what()) + ". Exiting.\n", daemon);
result = EXIT_FAILURE;
}
catch(...)
{
display_fatal_err("Unexpected error, Exiting\n", daemon);
result = EXIT_FAILURE;
}
@@ -445,9 +546,11 @@ int falco_init(int argc, char **argv)
exit:
delete inspector;
delete engine;
delete outputs;
if(ls)
{
lua_close(ls);
}
return result;
}

View File

@@ -1,109 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include "falco_outputs.h"
#include "config_falco.h"
#include "formats.h"
#include "logger.h"
using namespace std;
falco_outputs::falco_outputs()
{
}
falco_outputs::~falco_outputs()
{
}
void falco_outputs::init(bool json_output)
{
// The engine must have been given an inspector by now.
if(! m_inspector)
{
throw falco_exception("No inspector provided");
}
falco_common::init(m_lua_main_filename.c_str(), FALCO_SOURCE_LUA_DIR);
falco_formats::init(m_inspector, m_ls, json_output);
falco_logger::init(m_ls);
}
void falco_outputs::add_output(output_config oc)
{
uint8_t nargs = 1;
lua_getglobal(m_ls, m_lua_add_output.c_str());
if(!lua_isfunction(m_ls, -1))
{
throw falco_exception("No function " + m_lua_add_output + " found. ");
}
lua_pushstring(m_ls, oc.name.c_str());
// If we have options, build up a lua table containing them
if (oc.options.size())
{
nargs = 2;
lua_createtable(m_ls, 0, oc.options.size());
for (auto it = oc.options.cbegin(); it != oc.options.cend(); ++it)
{
lua_pushstring(m_ls, (*it).second.c_str());
lua_setfield(m_ls, -2, (*it).first.c_str());
}
}
if(lua_pcall(m_ls, nargs, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
throw falco_exception(string(lerr));
}
}
void falco_outputs::handle_event(sinsp_evt *ev, string &level, string &priority, string &format)
{
lua_getglobal(m_ls, m_lua_output_event.c_str());
if(lua_isfunction(m_ls, -1))
{
lua_pushlightuserdata(m_ls, ev);
lua_pushstring(m_ls, level.c_str());
lua_pushstring(m_ls, priority.c_str());
lua_pushstring(m_ls, format.c_str());
if(lua_pcall(m_ls, 4, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function output: " + string(lerr);
throw falco_exception(err);
}
}
else
{
throw falco_exception("No function " + m_lua_output_event + " found in lua compiler module");
}
}

View File

@@ -1,57 +0,0 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "falco_common.h"
//
// This class acts as the primary interface between a program and the
// falco output engine. The falco rules engine is implemented by a
// separate class falco_engine.
//
class falco_outputs : public falco_common
{
public:
falco_outputs();
virtual ~falco_outputs();
// The way to refer to an output (file, syslog, stdout,
// etc). An output has a name and set of options.
struct output_config
{
std::string name;
std::map<std::string, std::string> options;
};
void init(bool json_output);
void add_output(output_config oc);
//
// ev is an event that has matched some rule. Pass the event
// to all configured outputs.
//
void handle_event(sinsp_evt *ev, std::string &level, std::string &priority, std::string &format);
private:
std::string m_lua_add_output = "add_output";
std::string m_lua_output_event = "output_event";
std::string m_lua_main_filename = "output.lua";
};

View File

@@ -0,0 +1,76 @@
#include "fields.h"
#include "chisel_api.h"
#include "filterchecks.h"
extern sinsp_filter_check_list g_filterlist;
const static struct luaL_reg ll_falco [] =
{
{"field", &falco_fields::field},
{NULL,NULL}
};
sinsp* falco_fields::s_inspector = NULL;
std::map<string, sinsp_filter_check*> falco_fields::s_fieldname_map;
void falco_fields::init(sinsp* inspector, lua_State *ls)
{
s_inspector = inspector;
luaL_openlib(ls, "falco", ll_falco, 0);
}
int falco_fields::field(lua_State *ls)
{
sinsp_filter_check* chk=NULL;
if (!lua_islightuserdata(ls, 1))
{
string err = "invalid argument passed to falco.field()";
fprintf(stderr, "%s\n", err.c_str());
throw sinsp_exception("falco.field() error");
}
sinsp_evt* evt = (sinsp_evt*)lua_topointer(ls, 1);
string fieldname = luaL_checkstring(ls, 2);
if (s_fieldname_map.count(fieldname) == 0)
{
chk = g_filterlist.new_filter_check_from_fldname(fieldname,
s_inspector,
false);
if(chk == NULL)
{
string err = "nonexistent fieldname passed to falco.field(): " + string(fieldname);
fprintf(stderr, "%s\n", err.c_str());
throw sinsp_exception("falco.field() error");
}
chk->parse_field_name(fieldname.c_str(), true);
s_fieldname_map[fieldname] = chk;
}
else
{
chk = s_fieldname_map[fieldname];
}
uint32_t vlen;
uint8_t* rawval = chk->extract(evt, &vlen);
if(rawval != NULL)
{
return lua_cbacks::rawval_to_lua_stack(ls, rawval, chk->get_field_info(), vlen);
}
else
{
lua_pushnil(ls);
return 1;
}
}

21
userspace/falco/fields.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "sinsp.h"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
class falco_fields
{
public:
static void init(sinsp* inspector, lua_State *ls);
// value = falco.field(evt, fieldname)
static int field(lua_State *ls);
static sinsp* s_inspector;
static std::map<string, sinsp_filter_check*> s_fieldname_map;
};

View File

@@ -1,26 +1,7 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include <json/json.h>
#include "formats.h"
#include "logger.h"
#include "falco_engine.h"
sinsp* falco_formats::s_inspector = NULL;
@@ -29,7 +10,6 @@ bool s_json_output = false;
const static struct luaL_reg ll_falco [] =
{
{"formatter", &falco_formats::formatter},
{"free_formatter", &falco_formats::free_formatter},
{"format_event", &falco_formats::format_event},
{NULL,NULL}
};
@@ -52,7 +32,9 @@ int falco_formats::formatter(lua_State *ls)
}
catch(sinsp_exception& e)
{
throw falco_exception("Invalid output format '" + format + "'.\n");
falco_logger::log(LOG_ERR, "Invalid output format '" + format + "'.\n");
throw sinsp_exception("set_formatter error");
}
lua_pushlightuserdata(ls, formatter);
@@ -60,20 +42,6 @@ int falco_formats::formatter(lua_State *ls)
return 1;
}
int falco_formats::free_formatter(lua_State *ls)
{
if (!lua_islightuserdata(ls, -1))
{
throw falco_exception("Invalid argument passed to free_formatter");
}
sinsp_evt_formatter *formatter = (sinsp_evt_formatter *) lua_topointer(ls, 1);
delete(formatter);
return 1;
}
int falco_formats::format_event (lua_State *ls)
{
string line;
@@ -82,7 +50,8 @@ int falco_formats::format_event (lua_State *ls)
!lua_isstring(ls, -2) ||
!lua_isstring(ls, -3) ||
!lua_islightuserdata(ls, -4)) {
throw falco_exception("Invalid arguments passed to format_event()\n");
falco_logger::log(LOG_ERR, "Invalid arguments passed to format_event()\n");
throw sinsp_exception("format_event error");
}
sinsp_evt* evt = (sinsp_evt*)lua_topointer(ls, 1);
const char *rule = (char *) lua_tostring(ls, 2);

View File

@@ -1,21 +1,3 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "sinsp.h"
@@ -36,9 +18,6 @@ class falco_formats
// formatter = falco.formatter(format_string)
static int formatter(lua_State *ls);
// falco.free_formatter(formatter)
static int free_formatter(lua_State *ls);
// formatted_string = falco.format_event(evt, formatter)
static int format_event(lua_State *ls);

View File

@@ -1,24 +1,9 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctime>
#include "logger.h"
#include "chisel_api.h"
#include "filterchecks.h"
const static struct luaL_reg ll_falco [] =
{

View File

@@ -1,21 +1,3 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "sinsp.h"

View File

@@ -1,20 +1,3 @@
--
-- Copyright (C) 2016 Draios inc.
--
-- This file is part of falco.
--
-- falco is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 2 as
-- published by the Free Software Foundation.
--
-- falco is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with falco. If not, see <http://www.gnu.org/licenses/>.
local parser = require("parser")
local compiler = {}
@@ -160,7 +143,7 @@ function check_for_ignored_syscalls_events(ast, filter_type, source)
(node.left.value == "evt.type" or
node.left.value == "syscall.type") then
if node.operator == "in" or node.operator == "pmatch" then
if node.operator == "in" then
for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then
if node.left.value == "evt.type" then
@@ -217,7 +200,7 @@ function get_evttypes(name, ast, source)
if found_not then
found_event_after_not = true
end
if node.operator == "in" or node.operator == "pmatch" then
if node.operator == "in" then
for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then
evtnames[v.value] = 1
@@ -290,7 +273,7 @@ function compiler.compile_macro(line, list_defs)
local ast, error_msg = parser.parse_filter(line)
if (error_msg) then
print ("Compilation error when compiling \""..line.."\": ", error_msg)
print ("Compilation error: ", error_msg)
error(error_msg)
end
@@ -315,7 +298,7 @@ function compiler.compile_filter(name, source, macro_defs, list_defs)
local ast, error_msg = parser.parse_filter(source)
if (error_msg) then
print ("Compilation error when compiling \""..source.."\": ", error_msg)
print ("Compilation error: ", error_msg)
error(error_msg)
end

View File

@@ -1,21 +1,3 @@
--
-- Copyright (C) 2016 Draios inc.
--
-- This file is part of falco.
--
-- falco is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 2 as
-- published by the Free Software Foundation.
--
-- falco is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with falco. If not, see <http://www.gnu.org/licenses/>.
local mod = {}
levels = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"}
@@ -24,7 +6,10 @@ mod.levels = levels
local outputs = {}
function mod.stdout(level, msg)
function mod.stdout(evt, rule, level, format)
format = "*%evt.time: "..levels[level+1].." "..format
formatter = falco.formatter(format)
msg = falco.format_event(evt, rule, levels[level+1], formatter)
print (msg)
end
@@ -41,17 +26,29 @@ function mod.file_validate(options)
end
function mod.file(level, msg, options)
function mod.file(evt, rule, level, format, options)
format = "*%evt.time: "..levels[level+1].." "..format
formatter = falco.formatter(format)
msg = falco.format_event(evt, rule, levels[level+1], formatter)
file = io.open(options.filename, "a+")
file:write(msg, "\n")
file:close()
end
function mod.syslog(level, msg, options)
function mod.syslog(evt, rule, level, format)
formatter = falco.formatter(format)
msg = falco.format_event(evt, rule, levels[level+1], formatter)
falco.syslog(level, msg)
end
function mod.program(level, msg, options)
function mod.program(evt, rule, level, format, options)
format = "*%evt.time: "..levels[level+1].." "..format
formatter = falco.formatter(format)
msg = falco.format_event(evt, rule, levels[level+1], formatter)
-- XXX Ideally we'd check that the program ran
-- successfully. However, the luajit we're using returns true even
-- when the shell can't run the program.
@@ -62,27 +59,10 @@ function mod.program(level, msg, options)
file:close()
end
local function level_of(s)
s = string.lower(s)
for i,v in ipairs(levels) do
if (string.find(string.lower(v), "^"..s)) then
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
end
end
error("Invalid severity level: "..s)
end
function output_event(event, rule, priority, format)
local level = level_of(priority)
format = "*%evt.time: "..levels[level+1].." "..format
formatter = falco.formatter(format)
msg = falco.format_event(event, rule, levels[level+1], formatter)
function mod.event(event, rule, level, format)
for index,o in ipairs(outputs) do
o.output(level, msg, o.config)
o.output(event, rule, level, format, o.config)
end
falco.free_formatter(formatter)
end
function add_output(output_name, config)

View File

@@ -1,20 +1,3 @@
--
-- Copyright (C) 2016 Draios inc.
--
-- This file is part of falco.
--
-- falco is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 2 as
-- published by the Free Software Foundation.
--
-- falco is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with falco. If not, see <http://www.gnu.org/licenses/>.
--[[
Falco grammar and parser.
@@ -216,7 +199,6 @@ local G = {
RelationalExpression =
rel(terminal "FieldName", V"RelOp", V"Value") +
rel(terminal "FieldName", V"InOp", V"InList") +
rel(terminal "FieldName", V"PmatchOp", V"InList") +
V"PrimaryExp";
PrimaryExp = symb("(") * V"Filter" * symb(")");
@@ -236,16 +218,14 @@ local G = {
idRest = alnum + P("_");
Identifier = V"idStart" * V"idRest"^0;
Macro = V"idStart" * V"idRest"^0 * -P".";
Int = digit^1;
PathString = (alnum + S'-_/*?')^1;
Index = V"Int" + V"PathString";
FieldName = V"Identifier" * (P"." + V"Identifier")^1 * (P"[" * V"Index" * P"]")^-1;
FieldName = V"Identifier" * (P"." + V"Identifier")^1 * (P"[" * V"Int" * P"]")^-1;
Name = C(V"Identifier") * -V"idRest";
Hex = (P("0x") + P("0X")) * xdigit^1;
Expo = S("eE") * S("+-")^-1 * digit^1;
Float = (((digit^1 * P(".") * digit^0) +
(P(".") * digit^1)) * V"Expo"^-1) +
(digit^1 * V"Expo");
Int = digit^1;
Number = C(V"Hex" + V"Float" + V"Int") /
function (n) return tonumber(n) end;
String = (P'"' * C(((P'\\' * P(1)) + (P(1) - P'"'))^0) * P'"' + P"'" * C(((P"\\" * P(1)) + (P(1) - P"'"))^0) * P"'") / function (s) return fix_str(s) end;
@@ -263,10 +243,8 @@ local G = {
symb(">") / ">" +
symb("contains") / "contains" +
symb("icontains") / "icontains" +
symb("glob") / "glob" +
symb("startswith") / "startswith";
InOp = kw("in") / "in";
PmatchOp = kw("pmatch") / "pmatch";
UnaryBoolOp = kw("not") / "not";
ExistsOp = kw("exists") / "exists";

View File

@@ -1,20 +1,3 @@
--
-- Copyright (C) 2016 Draios inc.
--
-- This file is part of falco.
--
-- falco is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 2 as
-- published by the Free Software Foundation.
--
-- falco is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with falco. If not, see <http://www.gnu.org/licenses/>.
--[[
Compile and install falco rules.
@@ -22,6 +5,7 @@
--]]
local output = require('output')
local compiler = require "compiler"
local yaml = require"lyaml"
@@ -89,7 +73,7 @@ local function install_filter(node, parent_bool_op)
filter.unnest() -- io.write(")")
elseif t == "BinaryRelOp" then
if (node.operator == "in" or node.operator == "pmatch") then
if (node.operator == "in") then
elements = map(function (el) return el.value end, node.right.elements)
filter.rel_expr(node.left.value, node.operator, elements, node.index)
else
@@ -117,23 +101,31 @@ function set_output(output_format, state)
end
end
local function priority(s)
s = string.lower(s)
for i,v in ipairs(output.levels) do
if (string.find(string.lower(v), "^"..s)) then
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
end
end
error("Invalid severity level: "..s)
end
-- Note that the rules_by_name and rules_by_idx refer to the same rule
-- object. The by_name index is used for things like describing rules,
-- and the by_idx index is used to map the relational node index back
-- to a rule.
local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}}
function load_rules(rules_content, rules_mgr, verbose, all_events)
function load_rules(filename, rules_mgr, verbose, all_events)
compiler.set_verbose(verbose)
compiler.set_all_events(all_events)
local rules = yaml.load(rules_content)
if rules == nil then
-- An empty rules file is acceptable
return
end
local f = assert(io.open(filename, "r"))
local s = f:read("*all")
f:close()
local rules = yaml.load(s)
for i,v in ipairs(rules) do -- iterate over yaml list
@@ -176,6 +168,8 @@ function load_rules(rules_content, rules_mgr, verbose, all_events)
end
end
-- Convert the priority as a string to a level now
v['level'] = priority(v['priority'])
state.rules_by_name[v['rule']] = v
local filter_ast, evttypes = compiler.compile_filter(v['rule'], v['condition'],
@@ -196,7 +190,7 @@ function load_rules(rules_content, rules_mgr, verbose, all_events)
install_filter(filter_ast.filter.value)
-- Pass the filter and event types back up
falco_rules.add_filter(rules_mgr, v['rule'], evttypes)
falco_rules.add_filter(rules_mgr, evttypes)
-- Rule ASTs are merged together into one big AST, with "OR" between each
-- rule.
@@ -205,15 +199,6 @@ function load_rules(rules_content, rules_mgr, verbose, all_events)
else
state.filter_ast = { type = "BinaryBoolOp", operator = "or", left = state.filter_ast, right = filter_ast.filter.value }
end
-- Enable/disable the rule
if (v['enabled'] == nil) then
v['enabled'] = true
end
if (v['enabled'] == false) then
falco_rules.enable_rule(rules_mgr, v['rule'], 0)
end
else
error ("Unexpected type in load_rule: "..filter_ast.type)
end
@@ -271,7 +256,11 @@ function describe_rule(name)
end
end
local rule_output_counts = {total=0, by_priority={}, by_name={}}
local rule_output_counts = {total=0, by_level={}, by_name={}}
for idx=0,table.getn(output.levels)-1,1 do
rule_output_counts.by_level[idx] = 0
end
function on_event(evt_, rule_id)
@@ -282,10 +271,10 @@ function on_event(evt_, rule_id)
rule_output_counts.total = rule_output_counts.total + 1
local rule = state.rules_by_idx[rule_id]
if rule_output_counts.by_priority[rule.priority] == nil then
rule_output_counts.by_priority[rule.priority] = 1
if rule_output_counts.by_level[rule.level] == nil then
rule_output_counts.by_level[rule.level] = 1
else
rule_output_counts.by_priority[rule.priority] = rule_output_counts.by_priority[rule.priority] + 1
rule_output_counts.by_level[rule.level] = rule_output_counts.by_level[rule.level] + 1
end
if rule_output_counts.by_name[rule.rule] == nil then
@@ -294,14 +283,17 @@ function on_event(evt_, rule_id)
rule_output_counts.by_name[rule.rule] = rule_output_counts.by_name[rule.rule] + 1
end
return rule.rule, rule.priority, rule.output
output.event(evt_, rule.rule, rule.level, rule.output)
end
function print_stats()
print("Events detected: "..rule_output_counts.total)
print("Rule counts by severity:")
for priority, count in pairs(rule_output_counts.by_priority) do
print (" "..priority..": "..count)
for idx, level in ipairs(output.levels) do
-- To keep the output concise, we only print 0 counts for error, warning, and info levels
if rule_output_counts.by_level[idx-1] > 0 or level == "Error" or level == "Warning" or level == "Informational" then
print (" "..level..": "..rule_output_counts.by_level[idx-1])
end
end
print("Triggered rules by rule name:")

View File

@@ -1,20 +1,3 @@
--
-- Copyright (C) 2016 Draios inc.
--
-- This file is part of falco.
--
-- falco is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 2 as
-- published by the Free Software Foundation.
--
-- falco is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with falco. If not, see <http://www.gnu.org/licenses/>.
local parser = require "parser"
if #arg ~= 1 then

View File

@@ -1,21 +1,3 @@
/*
Copyright (C) 2016 Draios inc.
This file is part of falco.
falco is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
falco is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with falco. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rules.h"
#include "logger.h"
@@ -25,18 +7,20 @@ extern "C" {
#include "lauxlib.h"
}
#include "falco_engine.h"
const static struct luaL_reg ll_falco_rules [] =
{
{"add_filter", &falco_rules::add_filter},
{"enable_rule", &falco_rules::enable_rule},
{NULL,NULL}
};
falco_rules::falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls)
: m_inspector(inspector), m_engine(engine), m_ls(ls)
falco_rules::falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename)
{
m_inspector = inspector;
m_ls = ls;
m_lua_parser = new lua_parser(inspector, m_ls);
load_compiler(lua_main_filename);
}
void falco_rules::init(lua_State *ls)
@@ -46,15 +30,14 @@ void falco_rules::init(lua_State *ls)
int falco_rules::add_filter(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
if (! lua_islightuserdata(ls, -2) ||
! lua_istable(ls, -1))
{
throw falco_exception("Invalid arguments passed to add_filter()\n");
falco_logger::log(LOG_ERR, "Invalid arguments passed to add_filter()\n");
throw sinsp_exception("add_filter error");
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
const char *rulec = lua_tostring(ls, -2);
falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
list<uint32_t> evttypes;
@@ -68,47 +51,44 @@ int falco_rules::add_filter(lua_State *ls)
lua_pop(ls, 1);
}
std::string rule = rulec;
rules->add_filter(rule, evttypes);
rules->add_filter(evttypes);
return 0;
}
void falco_rules::add_filter(string &rule, list<uint32_t> &evttypes)
void falco_rules::add_filter(list<uint32_t> &evttypes)
{
// While the current rule was being parsed, a sinsp_filter
// object was being populated by lua_parser. Grab that filter
// and pass it to the engine.
// and pass it to the inspector.
sinsp_filter *filter = m_lua_parser->get_filter(true);
m_engine->add_evttype_filter(rule, evttypes, filter);
m_inspector->add_evttype_filter(evttypes, filter);
}
int falco_rules::enable_rule(lua_State *ls)
void falco_rules::load_compiler(string lua_main_filename)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isnumber(ls, -1))
ifstream is;
is.open(lua_main_filename);
if(!is.is_open())
{
throw falco_exception("Invalid arguments passed to enable_rule()\n");
throw sinsp_exception("can't open file " + lua_main_filename);
}
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);
string scriptstr((istreambuf_iterator<char>(is)),
istreambuf_iterator<char>());
rules->enable_rule(rule, enabled);
return 0;
//
// Load the compiler script
//
if(luaL_loadstring(m_ls, scriptstr.c_str()) || lua_pcall(m_ls, 0, 0, 0))
{
throw sinsp_exception("Failed to load script " +
lua_main_filename + ": " + lua_tostring(m_ls, -1));
}
}
void falco_rules::enable_rule(string &rule, bool enabled)
{
m_engine->enable_rule(rule, enabled);
}
void falco_rules::load_rules(const string &rules_content, bool verbose, bool all_events)
void falco_rules::load_rules(string rules_filename, bool verbose, bool all_events)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
@@ -178,7 +158,7 @@ void falco_rules::load_rules(const string &rules_content, bool verbose, bool all
lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str());
lua_pushstring(m_ls, rules_content.c_str());
lua_pushstring(m_ls, rules_filename.c_str());
lua_pushlightuserdata(m_ls, this);
lua_pushboolean(m_ls, (verbose ? 1 : 0));
lua_pushboolean(m_ls, (all_events ? 1 : 0));
@@ -186,10 +166,10 @@ void falco_rules::load_rules(const string &rules_content, bool verbose, bool all
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error loading rules:" + string(lerr);
throw falco_exception(err);
throw sinsp_exception(err);
}
} else {
throw falco_exception("No function " + m_lua_load_rules + " found in lua rule module");
throw sinsp_exception("No function " + m_lua_load_rules + " found in lua rule module");
}
}
@@ -209,14 +189,19 @@ void falco_rules::describe_rule(std::string *rule)
{
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);
throw sinsp_exception(err);
}
} else {
throw falco_exception("No function " + m_lua_describe_rule + " found in lua rule module");
throw sinsp_exception("No function " + m_lua_describe_rule + " found in lua rule module");
}
}
sinsp_filter* falco_rules::get_filter()
{
return m_lua_parser->get_filter();
}
falco_rules::~falco_rules()
{
delete m_lua_parser;

35
userspace/falco/rules.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include <list>
#include "sinsp.h"
#include "lua_parser.h"
class falco_rules
{
public:
falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename);
~falco_rules();
void load_rules(string rules_filename, bool verbose, bool all_events);
void describe_rule(string *rule);
sinsp_filter* get_filter();
static void init(lua_State *ls);
static int add_filter(lua_State *ls);
private:
void load_compiler(string lua_main_filename);
void add_filter(list<uint32_t> &evttypes);
lua_parser* m_lua_parser;
sinsp* m_inspector;
lua_State* m_ls;
string m_lua_load_rules = "load_rules";
string m_lua_ignored_syscalls = "ignored_syscalls";
string m_lua_ignored_events = "ignored_events";
string m_lua_events = "events";
string m_lua_on_event = "on_event";
string m_lua_describe_rule = "describe_rule";
};