diff --git a/CMakeCPackOptions.cmake b/CMakeCPackOptions.cmake index 91854961..5d50761d 100644 --- a/CMakeCPackOptions.cmake +++ b/CMakeCPackOptions.cmake @@ -1,3 +1,13 @@ +if(CPACK_GENERATOR MATCHES "DEB") + list(APPEND CPACK_INSTALL_COMMANDS "mkdir -p _CPack_Packages/${CPACK_TOPLEVEL_TAG}/${CPACK_GENERATOR}/${CPACK_PACKAGE_FILE_NAME}/etc/init.d/") + list(APPEND CPACK_INSTALL_COMMANDS "cp scripts/debian/falco _CPack_Packages/${CPACK_TOPLEVEL_TAG}/${CPACK_GENERATOR}/${CPACK_PACKAGE_FILE_NAME}/etc/init.d") +endif() + +if(CPACK_GENERATOR MATCHES "RPM") + list(APPEND CPACK_INSTALL_COMMANDS "mkdir -p _CPack_Packages/${CPACK_TOPLEVEL_TAG}/${CPACK_GENERATOR}/${CPACK_PACKAGE_FILE_NAME}/etc/rc.d/init.d/") + list(APPEND CPACK_INSTALL_COMMANDS "cp scripts/rpm/falco _CPack_Packages/${CPACK_TOPLEVEL_TAG}/${CPACK_GENERATOR}/${CPACK_PACKAGE_FILE_NAME}/etc/rc.d/init.d") +endif() + if(CPACK_GENERATOR MATCHES "TGZ") set(CPACK_SET_DESTDIR "ON") set(CPACK_STRIP_FILES "OFF") diff --git a/CMakeLists.txt b/CMakeLists.txt index fb86493f..22c3e5ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,6 +181,7 @@ add_subdirectory(${SYSDIG_DIR}/userspace/libscap ${PROJECT_BINARY_DIR}/userspace add_subdirectory(${SYSDIG_DIR}/userspace/libsinsp ${PROJECT_BINARY_DIR}/userspace/libsinsp) add_subdirectory(rules) +add_subdirectory(scripts) add_subdirectory(userspace/falco) @@ -192,18 +193,23 @@ set(CPACK_PACKAGE_VERSION "${FALCO_VERSION}") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}") set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake") set(CPACK_STRIP_FILES "ON") +set(CPACK_PACKAGE_RELOCATABLE "OFF") set(CPACK_GENERATOR DEB RPM TGZ) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Sysdig ") set(CPACK_DEBIAN_PACKAGE_SECTION "utils") - set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://www.sysdig.org") set(CPACK_DEBIAN_PACKAGE_DEPENDS "sysdig") +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${PROJECT_SOURCE_DIR}/scripts/debian/postinst;${PROJECT_SOURCE_DIR}/scripts/debian/prerm;${PROJECT_SOURCE_DIR}/scripts/debian/postrm") set(CPACK_RPM_PACKAGE_LICENSE "GPLv2") set(CPACK_RPM_PACKAGE_URL "http://www.sysdig.org") set(CPACK_RPM_PACKAGE_REQUIRES "sysdig") -set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION /usr/src /usr/share/man /usr/share/man/man8) +set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/scripts/rpm/postinstall") +set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/scripts/rpm/preuninstall") +set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/scripts/rpm/postuninstall") +set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION /usr/src /usr/share/man /usr/share/man/man8 /etc /usr /usr/bin /usr/share /etc/rc.d /etc/rc.d/init.d ) +set(CPACK_RPM_PACKAGE_RELOCATABLE "OFF") include(CPack) diff --git a/falco.yaml b/falco.yaml index d82ed41e..719e4598 100644 --- a/falco.yaml +++ b/falco.yaml @@ -1,16 +1,16 @@ -rules_file: /etc/falco_rules.conf +rules_file: /etc/falco_rules.yaml json_output: false -log_stderr: true +log_stderr: false log_syslog: true syslog_output: - enabled: false + enabled: true file_output: - enabled: true + enabled: false filename: ./events.txt stdout_output: - enabled: true + enabled: false diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt new file mode 100644 index 00000000..f9084aee --- /dev/null +++ b/scripts/CMakeLists.txt @@ -0,0 +1,5 @@ +file(COPY ${PROJECT_SOURCE_DIR}/scripts/debian/falco + DESTINATION ${PROJECT_BINARY_DIR}/scripts/debian) + +file(COPY ${PROJECT_SOURCE_DIR}/scripts/rpm/falco + DESTINATION ${PROJECT_BINARY_DIR}/scripts/rpm) diff --git a/scripts/debian/falco b/scripts/debian/falco new file mode 100755 index 00000000..0c55a02f --- /dev/null +++ b/scripts/debian/falco @@ -0,0 +1,156 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: falco +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Falco syscall activity monitoring agent +# Description: Falco is a system activity monitoring agent +# driven by system calls with support for containers. +### END INIT INFO + +# Author: Sysdig + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Falco" +NAME=falco +DAEMON=/usr/bin/$NAME +PIDFILE=/var/run/$NAME.pid +DAEMON_ARGS="--daemon --pidfile=$PIDFILE" +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/scripts/debian/postinst b/scripts/debian/postinst new file mode 100755 index 00000000..0084f213 --- /dev/null +++ b/scripts/debian/postinst @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +NAME=falco + +if [ -x "/etc/init.d/$NAME" ]; then + update-rc.d $NAME defaults >/dev/null +fi + diff --git a/scripts/debian/postrm b/scripts/debian/postrm new file mode 100755 index 00000000..18797f67 --- /dev/null +++ b/scripts/debian/postrm @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +NAME=falco + +if [ "$1" = "purge" ] ; then + update-rc.d $NAME remove >/dev/null +fi diff --git a/scripts/debian/prerm b/scripts/debian/prerm new file mode 100755 index 00000000..5270e189 --- /dev/null +++ b/scripts/debian/prerm @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +NAME=falco + +if [ -x "/etc/init.d/$NAME" ]; then + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then + invoke-rc.d $NAME stop || exit $? + else + /etc/init.d/$NAME stop || exit $? + fi +fi + diff --git a/scripts/rpm/falco b/scripts/rpm/falco new file mode 100755 index 00000000..524e167c --- /dev/null +++ b/scripts/rpm/falco @@ -0,0 +1,105 @@ +#!/bin/sh +# +# falco syscall monitoring agent +# +# chkconfig: 2345 55 45 +# description: Falco syscall monitoring agent +# + +### BEGIN INIT INFO +# Provides: +# Required-Start: +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: +# Default-Stop: +# Short-Description: +# Description: +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +exec="/usr/bin/falco" +prog="falco" +# config="" + +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog + +lockfile=/var/lock/subsys/$prog +pidfile="/var/run/falco.pid" + +start() { + [ -x $exec ] || exit 5 + # [ -f $config ] || exit 6 + echo -n $"Starting $prog: " + daemon $exec --daemon --pidfile=$pidfile + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval +} + +stop() { + echo -n $"Stopping $prog: " + killproc -p $pidfile + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +restart() { + stop + start +} + +reload() { + restart +} + +force_reload() { + restart +} + +rh_status() { + status -p $pidfile $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? diff --git a/scripts/rpm/postinstall b/scripts/rpm/postinstall new file mode 100755 index 00000000..56e0be45 --- /dev/null +++ b/scripts/rpm/postinstall @@ -0,0 +1 @@ +/sbin/chkconfig --add falco diff --git a/scripts/rpm/postuninstall b/scripts/rpm/postuninstall new file mode 100755 index 00000000..69502ec9 --- /dev/null +++ b/scripts/rpm/postuninstall @@ -0,0 +1,3 @@ +if [ "$1" -ge "1" ]; then + /sbin/service falco condrestart > /dev/null 2>&1 +fi diff --git a/scripts/rpm/preuninstall b/scripts/rpm/preuninstall new file mode 100755 index 00000000..10d3b98f --- /dev/null +++ b/scripts/rpm/preuninstall @@ -0,0 +1,4 @@ +if [ $1 = 0 ]; then + /sbin/service falco stop > /dev/null 2>&1 + /sbin/chkconfig --del falco +fi diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index b1c5a6cb..74e5775e 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -19,7 +19,7 @@ void falco_configuration::init(string conf_filename) string m_config_file = conf_filename; m_config = new yaml_configuration(m_config_file); - m_rules_filename = m_config->get_scalar("rules_file", "/etc/falco_rules.conf"); + m_rules_filename = m_config->get_scalar("rules_file", "/etc/falco_rules.yaml"); m_json_output = m_config->get_scalar("json_output", false); output_config file_output; diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 8b3cb59e..9dce811c 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -39,11 +39,13 @@ static void usage() printf( "Usage: falco [options] rules_filename\n\n" "Options:\n" - " -h, --help Print this page\n" - " -c Configuration file (default " FALCO_SOURCE_CONF_FILE ", " FALCO_INSTALL_CONF_FILE ")\n" - " -o Output type (options are 'stdout', 'syslog', default is 'stdout')\n" - " -e Read the events from (in .scap format) instead of tapping into live.\n" - " -r Rules file (defaults to value set in configuration file, or /etc/falco_rules.conf).\n" + " -h, --help Print this page\n" + " -c Configuration file (default " FALCO_SOURCE_CONF_FILE ", " FALCO_INSTALL_CONF_FILE ")\n" + " -o Output type (options are 'stdout', 'syslog', default is 'stdout')\n" + " -d, --daemon Run as a daemon\n" + " -p, --pidfile When run as a daemon, write pid to specified file\n" + " -e Read the events from (in .scap format) instead of tapping into live.\n" + " -r Rules file (defaults to value set in configuration file, or /etc/falco_rules.conf).\n" "\n" ); } @@ -192,16 +194,20 @@ int falco_init(int argc, char **argv) sinsp_evt::param_fmt event_buffer_format; int long_index = 0; string lua_main_filename; - string output_name = "stdout"; + string output_name = "syslog"; string scap_filename; string conf_filename; string rules_filename; string lua_dir = FALCO_LUA_DIR; lua_State* ls = NULL; + bool daemon = false; + string pidfilename = "/var/run/falco.pid"; static struct option long_options[] = { {"help", no_argument, 0, 'h' }, + {"daemon", no_argument, 0, 'd' }, + {"pidfile", required_argument, 0, 'd' }, {0, 0, 0, 0} }; @@ -214,7 +220,7 @@ int falco_init(int argc, char **argv) // Parse the args // while((op = getopt_long(argc, argv, - "c:ho:e:r:", + "c:ho:e:r:dp:", long_options, &long_index)) != -1) { switch(op) @@ -239,6 +245,12 @@ int falco_init(int argc, char **argv) case 'r': rules_filename = optarg; break; + case 'd': + daemon = true; + break; + case 'p': + pidfilename = optarg; + break; case '?': result = EXIT_FAILURE; goto exit; @@ -248,6 +260,18 @@ int falco_init(int argc, char **argv) } + // Some combinations of arguments are not allowed. + if (daemon && pidfilename == "") { + falco_logger::log(LOG_ERR, "If -d is provided, a pid file must also be provided. Exiting.\n"); + result = EXIT_FAILURE; + goto exit; + } + + if (daemon && output_name == "stdout") { + falco_logger::log(LOG_ERR, "If -d is provided, can not output to stdout. Exiting.\n"); + result = EXIT_FAILURE; + goto exit; + } ifstream* conf_stream; if (conf_filename.size()) @@ -255,7 +279,7 @@ int falco_init(int argc, char **argv) conf_stream = new ifstream(conf_filename); if (!conf_stream->good()) { - falco_logger::log(LOG_ERR, "Could not find configuration file at " + conf_filename + ". Exiting \n"); + falco_logger::log(LOG_ERR, "Could not find configuration file at " + conf_filename + ". Exiting.\n"); result = EXIT_FAILURE; goto exit; } @@ -308,7 +332,7 @@ int falco_init(int argc, char **argv) { falco_logger::log(LOG_ERR, "Could not find Falco Lua libraries (tried " + string(FALCO_LUA_DIR FALCO_LUA_MAIN) + ", " + - lua_main_filename + "). Exiting \n"); + lua_main_filename + "). Exiting.\n"); result = EXIT_FAILURE; goto exit; } @@ -365,11 +389,64 @@ int falco_init(int argc, char **argv) { if(system("modprobe " PROBE_NAME " > /dev/null 2> /dev/null")) { - falco_logger::log(LOG_ERR, "Unable to load the driver. Exiting\n"); + falco_logger::log(LOG_ERR, "Unable to load the driver. Exiting.\n"); } inspector->open(); } } + + // If daemonizing, do it here so any init errors will + // be returned in the foreground process. + if (daemon) { + pid_t pid, sid; + + pid = fork(); + if (pid < 0) { + // error + falco_logger::log(LOG_ERR, "Could not fork. Exiting.\n"); + result = EXIT_FAILURE; + goto exit; + } else if (pid > 0) { + // parent. Write child pid to pidfile and exit + std::ofstream pidfile; + pidfile.open(pidfilename); + + if (!pidfile.good()) + { + falco_logger::log(LOG_ERR, "Could not write pid to pid file " + pidfilename + ". Exiting.\n"); + result = EXIT_FAILURE; + goto exit; + } + pidfile << pid; + pidfile.close(); + goto exit; + } + // if here, child. + + // Become own process group. + sid = setsid(); + if (sid < 0) { + falco_logger::log(LOG_ERR, "Could not set session id. Exiting.\n"); + result = EXIT_FAILURE; + goto exit; + } + + // Set umask so no files are world anything or group writable. + umask(027); + + // Change working directory to '/' + if ((chdir("/")) < 0) { + falco_logger::log(LOG_ERR, "Could not change working directory to '/'. Exiting.\n"); + result = EXIT_FAILURE; + goto exit; + } + + // Close stdin, stdout, stderr. + close(0); + close(1); + close(2); + } + do_inspect(inspector, rules, ls); @@ -378,7 +455,7 @@ int falco_init(int argc, char **argv) } catch(sinsp_exception& e) { - falco_logger::log(LOG_ERR, "Runtime error: " + string(e.what()) + ". Exiting\n"); + falco_logger::log(LOG_ERR, "Runtime error: " + string(e.what()) + ". Exiting.\n"); result = EXIT_FAILURE; } diff --git a/userspace/falco/logger.cpp b/userspace/falco/logger.cpp index 9f104265..7c4bdc5b 100644 --- a/userspace/falco/logger.cpp +++ b/userspace/falco/logger.cpp @@ -30,8 +30,8 @@ int falco_logger::syslog(lua_State *ls) { return 0; } -bool falco_logger::log_stderr; -bool falco_logger::log_syslog; +bool falco_logger::log_stderr = true; +bool falco_logger::log_syslog = true; void falco_logger::log(int priority, const string msg) { if (falco_logger::log_syslog) {