From 880c39633def5ea7272a4e9a82bb2b0c18fa1763 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 13 Oct 2016 14:48:32 -0700 Subject: [PATCH] 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. --- rules/falco_rules.yaml | 17 +++- userspace/falco/falco.cpp | 143 ++++++++++++++++++++++++++++-- userspace/falco/falco_outputs.cpp | 39 +++++++- userspace/falco/falco_outputs.h | 4 + 4 files changed, 191 insertions(+), 12 deletions(-) diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 4dad5d2bb96..7f670f3e71e 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index fe41e3739b6..8fbc315839e 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -60,10 +60,40 @@ static void usage() " -d, --daemon Run as a daemon\n" " -D Disable any rules matching the regex . Can be specified multiple times.\n" " -e Read the events from (in .scap format) instead of tapping into live.\n" + " -k , --k8s-api=\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 | :[:], --k8s-api-cert= | :[:]\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 the rule with name and exit.\n" + " -m , --mesos-api=\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 = Set the value of option to . Overrides values in configuration file.\n" " can be a two-part .\n" + " -p , --print=\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 When run as a daemon, write pid to specified file\n" " -r 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" @@ -169,12 +199,21 @@ 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'}, + {"print", required_argument, 0, 'p' }, {"pidfile", required_argument, 0, 'P' }, {0, 0, 0, 0} @@ -182,13 +221,6 @@ 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 disabled_rule_patterns; string pattern; @@ -219,7 +251,14 @@ int falco_init(int argc, char **argv) break; case 'e': scap_filename = optarg; + k8s_api = new string(); + mesos_api = new string(); break; + case 'k': + k8s_api = new string(optarg); + break; + case 'K': + k8s_api_cert = new string(optarg); break; case 'L': describe_all_rules = true; @@ -227,12 +266,37 @@ int falco_init(int argc, char **argv) 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; @@ -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. if (daemon && pidfilename == "") { 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); } + // + // 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); diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index 03f6b8a7bd2..545ade7517a 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -27,6 +27,7 @@ along with falco. If not, see . using namespace std; 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); } +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; @@ -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) { diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h index 247d837c831..1f13f653489 100644 --- a/userspace/falco/falco_outputs.h +++ b/userspace/falco/falco_outputs.h @@ -44,6 +44,8 @@ class falco_outputs : public falco_common 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 // to all configured outputs. @@ -54,4 +56,6 @@ class falco_outputs : public falco_common std::string m_lua_add_output = "add_output"; std::string m_lua_output_event = "output_event"; std::string m_lua_main_filename = "output.lua"; + std::string m_extra; + bool m_replace_container_info; };