Add k8s/mesos/container info to rule outputs

Copy handling of -pk/-pm/-pc/-k/-m arguments from sysdig. All of the
relevant code was already in the inspector so that was easy.

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.
This commit is contained in:
Mark Stemm 2016-10-13 14:48:32 -07:00
parent c6b433c2df
commit 5f9f5c47d1
4 changed files with 191 additions and 12 deletions

View File

@ -164,6 +164,15 @@
# System # System
- macro: modules - macro: modules
condition: evt.type in (delete_module, init_module) 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 - macro: container
condition: container.id != host condition: container.id != host
- macro: interactive - macro: interactive
@ -265,7 +274,7 @@
- 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. 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) 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 priority: WARNING
- rule: Run shell untrusted - rule: Run shell untrusted
@ -280,7 +289,7 @@
- rule: File Open by Privileged Container - rule: File Open by Privileged Container
desc: Any open by a privileged container. Exceptions are made for known trusted images. 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 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 priority: WARNING
- macro: sensitive_mount - macro: sensitive_mount
@ -289,7 +298,7 @@
- rule: Sensitive Mount by Container - 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. 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 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 priority: WARNING
# Anything run interactively by root # Anything run interactively by root
@ -306,7 +315,7 @@
- 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. 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 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 priority: WARNING
# sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets # sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets

View File

@ -60,10 +60,40 @@ static void usage()
" -d, --daemon Run as a daemon\n" " -d, --daemon Run as a daemon\n"
" -D <pattern> Disable any rules matching the regex <pattern>. Can be specified multiple times.\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" " -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 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" " -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" " -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" " <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" " -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" " -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" " Can be specified multiple times to read from multiple files.\n"
@ -169,12 +199,21 @@ int falco_init(int argc, char **argv)
string describe_rule = ""; string describe_rule = "";
bool verbose = false; bool verbose = false;
bool all_events = 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[] = static struct option long_options[] =
{ {
{"help", no_argument, 0, 'h' }, {"help", no_argument, 0, 'h' },
{"daemon", no_argument, 0, 'd' }, {"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'}, {"option", required_argument, 0, 'o'},
{"print", required_argument, 0, 'p' },
{"pidfile", required_argument, 0, 'P' }, {"pidfile", required_argument, 0, 'P' },
{0, 0, 0, 0} {0, 0, 0, 0}
@ -182,13 +221,6 @@ int falco_init(int argc, char **argv)
try 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; set<string> disabled_rule_patterns;
string pattern; string pattern;
@ -219,7 +251,14 @@ int falco_init(int argc, char **argv)
break; break;
case 'e': case 'e':
scap_filename = optarg; scap_filename = optarg;
k8s_api = new string();
mesos_api = new string();
break; break;
case 'k':
k8s_api = new string(optarg);
break;
case 'K':
k8s_api_cert = new string(optarg);
break; break;
case 'L': case 'L':
describe_all_rules = true; describe_all_rules = true;
@ -227,12 +266,37 @@ int falco_init(int argc, char **argv)
case 'l': case 'l':
describe_rule = optarg; describe_rule = optarg;
break; break;
case 'm':
mesos_api = new string(optarg);
break;
case 'o': case 'o':
cmdline_options.push_back(optarg); cmdline_options.push_back(optarg);
break; break;
case 'P': case 'P':
pidfilename = optarg; pidfilename = optarg;
break; 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': case 'r':
rules_filenames.push_back(optarg); rules_filenames.push_back(optarg);
break; break;
@ -248,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. // Some combinations of arguments are not allowed.
if (daemon && pidfilename == "") { if (daemon && pidfilename == "") {
throw std::invalid_argument("If -d is provided, a pid file must also be provided"); throw std::invalid_argument("If -d is provided, a pid file must also be provided");
@ -428,6 +500,63 @@ int falco_init(int argc, char **argv)
open("/dev/null", O_RDWR); 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, do_inspect(engine,
outputs, outputs,
inspector); inspector);

View File

@ -27,6 +27,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
using namespace std; using namespace std;
falco_outputs::falco_outputs() falco_outputs::falco_outputs()
: m_replace_container_info(false)
{ {
} }
@ -51,6 +52,12 @@ void falco_outputs::init(bool json_output)
falco_logger::init(m_ls); 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) void falco_outputs::add_output(output_config oc)
{ {
uint8_t nargs = 1; uint8_t nargs = 1;
@ -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()); 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)) if(lua_isfunction(m_ls, -1))
{ {
lua_pushlightuserdata(m_ls, ev); lua_pushlightuserdata(m_ls, ev);
lua_pushstring(m_ls, level.c_str()); lua_pushstring(m_ls, level.c_str());
lua_pushstring(m_ls, priority.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) if(lua_pcall(m_ls, 4, 0, 0) != 0)
{ {

View File

@ -44,6 +44,8 @@ public:
void add_output(output_config oc); void add_output(output_config oc);
void set_extra(string &extra, bool replace_container_info);
// //
// ev is an event that has matched some rule. Pass the event // ev is an event that has matched some rule. Pass the event
// to all configured outputs. // to all configured outputs.
@ -54,4 +56,6 @@ private:
std::string m_lua_add_output = "add_output"; std::string m_lua_add_output = "add_output";
std::string m_lua_output_event = "output_event"; std::string m_lua_output_event = "output_event";
std::string m_lua_main_filename = "output.lua"; std::string m_lua_main_filename = "output.lua";
std::string m_extra;
bool m_replace_container_info;
}; };