Skip to content

Commit

Permalink
Add k8s/mesos/container info to rule outputs.
Browse files Browse the repository at this point in the history
Copy handling of -pk/-pm/-pc/-k <k8s url>/-m <marathon url> arguments
from sysdig. All of the relevant code was already in the inspector so
that was easy.

There are a lot of command line options now, so sort them alphabetically
in the usage and getopt handling to make them easier to find.

The information from k8s/mesos/containers is used in two ways:

 - In rule outputs, if the format string contains %container.info, that
   is replaced with the value from -pk/-pm/-pc, if one of those options
   was provided. If no option was provided, %container.info is replaced
   with a generic %container.name (id=%container.id) instead.

 - If the format string does not contain %container.info, and one of
   -pk/-pm/-pc was provided, that is added to the end of the formatting
   string.

 - If -p was specified with a general value (i.e. not
   kubernetes/mesos/container), the value is simply added to the end and
   any %container.info is replaced with the generic value.
  • Loading branch information
mstemm committed Oct 13, 2016
1 parent 1447894 commit 1adfac6
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 38 deletions.
17 changes: 13 additions & 4 deletions rules/falco_rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@
# System
- macro: modules
condition: evt.type in (delete_module, init_module)

# Use this to test whether the event occurred within a container.

# When displaying container information in the output field, use
# %container.info, without any leading term (file=%fd.name
# %container.info user=%user.name, and not file=%fd.name
# container=%container.info user=%user.name). The output will change
# based on the context and whether or not -pk/-pm/-pc was specified on
# the command line.
- macro: container
condition: container.id != host
- macro: interactive
Expand Down Expand Up @@ -265,7 +274,7 @@
- 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))"
output: "Namespace change (setns) by unexpected program (user=%user.name command=%proc.cmdline %container.info)"
priority: WARNING

- rule: Run shell untrusted
Expand All @@ -280,7 +289,7 @@
- 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)
output: File opened for read/write by non-privileged container (user=%user.name command=%proc.cmdline %container.info file=%fd.name)
priority: WARNING

- macro: sensitive_mount
Expand All @@ -289,7 +298,7 @@
- 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)
output: File opened for read/write by container mounting sensitive directory (user=%user.name command=%proc.cmdline %container.info file=%fd.name)
priority: WARNING

# Anything run interactively by root
Expand All @@ -306,7 +315,7 @@
- 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)
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)"
output: "Shell spawned in a container other than entrypoint (user=%user.name %container.info 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
Expand Down
196 changes: 163 additions & 33 deletions userspace/falco/falco.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,48 @@ static void usage()
"Options:\n"
" -h, --help Print this page\n"
" -c Configuration file (default " FALCO_SOURCE_CONF_FILE ", " FALCO_INSTALL_CONF_FILE ")\n"
" -o, --option <key>=<val> Set the value of option <key> to <val>. Overrides values in configuration file.\n"
" <key> can be a two-part <key>.<subkey>\n"
" -A Monitor all events, including those with EF_DROP_FALCO flag.\n"
" -d, --daemon Run as a daemon\n"
" -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"
" -e <events_file> Read the events from <events_file> (in .scap format) instead of tapping into live.\n"
" -k <url>, --k8s-api=<url>\n"
" Enable Kubernetes support by connecting to the API server\n"
" specified as argument. E.g. \"http://admin:password@127.0.0.1:8080\".\n"
" The API server can also be specified via the environment variable\n"
" FALCO_K8S_API.\n"
" -K <bt_file> | <cert_file>:<key_file[#password]>[:<ca_cert_file>], --k8s-api-cert=<bt_file> | <cert_file>:<key_file[#password]>[:<ca_cert_file>]\n"
" Use the provided files names to authenticate user and (optionally) verify the K8S API\n"
" server identity.\n"
" Each entry must specify full (absolute, or relative to the current directory) path\n"
" to the respective file.\n"
" Private key password is optional (needed only if key is password protected).\n"
" CA certificate is optional. For all files, only PEM file format is supported. \n"
" Specifying CA certificate only is obsoleted - when single entry is provided \n"
" for this option, it will be interpreted as the name of a file containing bearer token.\n"
" Note that the format of this command-line option prohibits use of files whose names contain\n"
" ':' or '#' characters in the file name.\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"
" -m <url[,marathon_url]>, --mesos-api=<url[,marathon_url]>\n"
" Enable Mesos support by connecting to the API server\n"
" specified as argument. E.g. \"http://admin:password@127.0.0.1:5050\".\n"
" Marathon url is optional and defaults to Mesos address, port 8080.\n"
" The API servers can also be specified via the environment variable\n"
" FALCO_MESOS_API.\n"
" -o, --option <key>=<val> Set the value of option <key> to <val>. Overrides values in configuration file.\n"
" <key> can be a two-part <key>.<subkey>\n"
" -p <output_format>, --print=<output_format>\n"
" Add additional information to each falco notification's output.\n"
" With -pc or -pcontainer will use a container-friendly format.\n"
" With -pk or -pkubernetes will use a kubernetes-friendly format.\n"
" With -pm or -pmesos will use a mesos-friendly format.\n"
" Additionally, specifying -pc/-pk/-pm will change the interpretation\n"
" of %%container.info in rule output fields\n"
" See the examples section below for more info.\n"
" -P, --pidfile <pid_file> When run as a daemon, write pid to specified file\n"
" -r <rules_file> Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\n"
" Can be specified multiple times to read from multiple files.\n"
" -v Verbose output.\n"
" -A Monitor all events, including those with EF_DROP_FALCO flag.\n"
"\n"
);
}
Expand Down Expand Up @@ -169,34 +199,36 @@ int falco_init(int argc, char **argv)
string describe_rule = "";
bool verbose = false;
bool all_events = false;
string* k8s_api = 0;
string* k8s_api_cert = 0;
string* mesos_api = 0;
string output_format = "";
bool replace_container_info = false;

static struct option long_options[] =
{
{"help", no_argument, 0, 'h' },
{"daemon", no_argument, 0, 'd' },
{"k8s-api", required_argument, 0, 'k'},
{"k8s-api-cert", required_argument, 0, 'K' },
{"mesos-api", required_argument, 0, 'm'},
{"option", required_argument, 0, 'o'},
{"pidfile", required_argument, 0, 'p' },
{"print", required_argument, 0, 'p' },
{"pidfile", required_argument, 0, 'P' },

{0, 0, 0, 0}
};

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",
"hc:AdD:e:k:K:Ll:m:o:P:p:r:v",
long_options, &long_index)) != -1)
{
switch(op)
Expand All @@ -207,37 +239,70 @@ int falco_init(int argc, char **argv)
case 'c':
conf_filename = optarg;
break;
case 'o':
cmdline_options.push_back(optarg);
break;
case 'e':
scap_filename = optarg;
case 'A':
all_events = true;
break;
case 'r':
rules_filenames.push_back(optarg);
case 'd':
daemon = true;
break;
case 'D':
pattern = optarg;
disabled_rule_patterns.insert(pattern);
break;
case 'd':
daemon = true;
case 'e':
scap_filename = optarg;
k8s_api = new string();
mesos_api = new string();
break;
case 'p':
pidfilename = optarg;
case 'k':
k8s_api = new string(optarg);
break;
case 'K':
k8s_api_cert = new string(optarg);
break;
case 'L':
describe_all_rules = true;
break;
case 'v':
verbose = true;
break;
case 'A':
all_events = true;
break;
case 'l':
describe_rule = optarg;
break;
case 'm':
mesos_api = new string(optarg);
break;
case 'o':
cmdline_options.push_back(optarg);
break;
case 'P':
pidfilename = optarg;
break;
case 'p':
if(string(optarg) == "c" || string(optarg) == "container")
{
output_format = "container=%container.name (id=%container.id)";
replace_container_info = true;
}
else if(string(optarg) == "k" || string(optarg) == "kubernetes")
{
output_format = "k8s.pod=%k8s.pod.name container=%container.id";
replace_container_info = true;
}
else if(string(optarg) == "m" || string(optarg) == "mesos")
{
output_format = "task=%mesos.task.name container=%container.id";
replace_container_info = true;
}
else
{
output_format = optarg;
replace_container_info = false;
}
break;
case 'r':
rules_filenames.push_back(optarg);
break;
case 'v':
verbose = true;
break;
case '?':
result = EXIT_FAILURE;
goto exit;
Expand All @@ -247,6 +312,14 @@ int falco_init(int argc, char **argv)

}

inspector = new sinsp();
engine = new falco_engine();
engine->set_inspector(inspector);

outputs = new falco_outputs();
outputs->set_inspector(inspector);
outputs->set_extra(output_format, replace_container_info);

// 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");
Expand Down Expand Up @@ -427,6 +500,63 @@ int falco_init(int argc, char **argv)
open("/dev/null", O_RDWR);
}

//
// run k8s, if required
//
if(k8s_api)
{
if(!k8s_api_cert)
{
if(char* k8s_cert_env = getenv("FALCO_K8S_API_CERT"))
{
k8s_api_cert = new string(k8s_cert_env);
}
}
inspector->init_k8s_client(k8s_api, k8s_api_cert, verbose);
k8s_api = 0;
k8s_api_cert = 0;
}
else if(char* k8s_api_env = getenv("FALCO_K8S_API"))
{
if(k8s_api_env != NULL)
{
if(!k8s_api_cert)
{
if(char* k8s_cert_env = getenv("FALCO_K8S_API_CERT"))
{
k8s_api_cert = new string(k8s_cert_env);
}
}
k8s_api = new string(k8s_api_env);
inspector->init_k8s_client(k8s_api, k8s_api_cert, verbose);
}
else
{
delete k8s_api;
delete k8s_api_cert;
}
k8s_api = 0;
k8s_api_cert = 0;
}

//
// run mesos, if required
//
if(mesos_api)
{
inspector->init_mesos_client(mesos_api, verbose);
}
else if(char* mesos_api_env = getenv("FALCO_MESOS_API"))
{
if(mesos_api_env != NULL)
{
mesos_api = new string(mesos_api_env);
inspector->init_mesos_client(mesos_api, verbose);
}
}
delete mesos_api;
mesos_api = 0;

do_inspect(engine,
outputs,
inspector);
Expand Down
39 changes: 38 additions & 1 deletion userspace/falco/falco_outputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
using namespace std;

falco_outputs::falco_outputs()
: m_replace_container_info(false)
{

}
Expand All @@ -51,6 +52,12 @@ void falco_outputs::init(bool json_output)
falco_logger::init(m_ls);
}

void falco_outputs::set_extra(string &extra, bool replace_container_info)
{
m_extra = extra;
m_replace_container_info = replace_container_info;
}

void falco_outputs::add_output(output_config oc)
{
uint8_t nargs = 1;
Expand Down Expand Up @@ -87,12 +94,42 @@ void falco_outputs::handle_event(sinsp_evt *ev, string &level, string &priority,
{
lua_getglobal(m_ls, m_lua_output_event.c_str());

// If the format string contains %container.info, replace it
// with extra. Otherwise, add extra onto the end of the format
// string.
string format_w_extra = format;
size_t pos;

if((pos = format_w_extra.find("%container.info")) != string::npos)
{
// There may not be any extra, or we're not supposed
// to replace it, in which case we use the generic
// "%container.name (id=%container.id)"
if(m_extra == "" || ! m_replace_container_info)
{
// 15 == strlen(%container.info)
format_w_extra.replace(pos, 15, "%container.name (id=%container.id)");
}
else
{
format_w_extra.replace(pos, 15, m_extra);
}
}
else
{
// Just add the extra to the end
if (m_extra != "")
{
format_w_extra += " " + m_extra;
}
}

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());
lua_pushstring(m_ls, format_w_extra.c_str());

if(lua_pcall(m_ls, 4, 0, 0) != 0)
{
Expand Down
Loading

0 comments on commit 1adfac6

Please sign in to comment.