Compare commits

...

151 Commits

Author SHA1 Message Date
Mark Stemm
0e3121b17c Embed .lua files into falco executable
Instead of having .lua files external to the program responsible for
loading rules, embed the contents of those files into the executable
and load them as strings instead of as files:

Add a cmake custom command below userspace/engine/lua that calls a
bash script lua-to-cpp.sh to generate falco_engine_lua_files.{cpp,hh}
that are compiled into the falco engine library.

The script creates a .cpp file that has const char * symbols for each
file, as well as lists of files that should be loaded when the falco
engine is loaded. There are actually two lists:

- lua_module_strings: these are loaded and also added to the lua
  runtime package.preload table, so they are available when lua code
  require()s them.

- lua_code_strings: these are loaded *and* evaluated, so the functions
  in them are availble to be called from C++.

This simplifies some of the falco_common methods, as there's no need
to keep track of a "main" lua file to load or paths from which the lua
loader should find files for modules, and there's no need to keep
track of an "alternate" lua directory that occurs for debug builds.

Also, there's no need to include any .lua files in the installed
packages, as they're built into the falco binary.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-01-12 12:14:47 -08:00
Mark Stemm
b05b252100 Clean up lyaml build a bit
change LYAML_SRC to LYAML_ROOT, which points to the top source
directory now.

LYAML_LIB and (new) LYAML_LUA_DIR are based relative to that
directory.

There's no install step at all now--the static library and the .lua
files are now used directly from the source tree.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-01-12 12:13:06 -08:00
Mark Stemm
2df9a68140 Move compiler/parser lua files to a "modules" subdir
This will distinguish it from rule_loader.lua, which is *not* a module
but lua code with functions that can be called directly.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-01-12 09:59:42 -08:00
Jason Dellaluce
0e52ef9971 fix(grpc): ignore protobuf deprecation warning
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-01-12 00:16:49 +01:00
Jason Dellaluce
a371a995b4 update(outputs): adapt grpc output to new protobuf definitions
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-01-12 00:16:49 +01:00
Jason Dellaluce
0f984c4dbe update(grpc): substitute and deprecate enum source field from protobuf
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-01-12 00:16:49 +01:00
Federico Di Pierro
48a23121df new(userspace/falco): add support for kernel side simple consumer.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2022-01-10 10:58:44 +01:00
Federico Di Pierro
475ed0dbeb fix(userspace/engine,userspace/falco): set http output contenttype to text/plain when json output is disabled
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2022-01-10 10:57:44 +01:00
Zach Stone
eaccfbe82d Pick some lint
Signed-off-by: Zach Stone <zach@giantswarm.io>
2022-01-10 10:56:44 +01:00
Zach Stone
e496c91562 Add Giant Swarm to Adopters list
Signed-off-by: Zach Stone <zach@giantswarm.io>
2022-01-10 10:56:44 +01:00
Lorenzo Susini
cef2c2d5c1 chore: improve --list output using is_source_valid
Signed-off-by: Lorenzo Susini <susinilorenzo1@gmail.com>
2022-01-10 10:53:44 +01:00
Jason Dellaluce
2ee0645f25 update(tests): remove token_bucket unit tests
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2022-01-04 16:41:18 +01:00
Mark Stemm
42f8b1cd83 Update to version of libs with better output formatting
This has required changes to print info on fields.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-12-23 17:05:39 +01:00
Mark Stemm
455be15b0b Fill in new shortdesc/data_type/tags for json fields
Update json_event_filter_factory::get_fields() to add the new
info (shortdesc, data_type, tags) to field descriptions.

This allows for richer outputs when printing info on the fields.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-12-23 17:05:39 +01:00
Mark Stemm
64e8feb200 Update fields checksum (no changes, order only)
With the new implementation of list_fields(), the order of fields
changed slightly. So update the checksum.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-12-23 17:05:39 +01:00
Mark Stemm
eded1062cd Use filter_fieldclass_info::as_string to print field info
Instead of having a falco-specific function to print field info, use
the built-in filter_fieldclass_info::as_string() instead. This is a
better implementation (displays addl info, has better wrapping, wider
output) and having a single implementation allows for consistent
outputs between falco and other potential programs that could use the libs.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-12-23 17:05:39 +01:00
Luca Guerra
473b94b386 fix(build): use consistent 7-character build abbrev sha
Signed-off-by: Luca Guerra <luca@guerra.sh>
2021-12-23 16:23:39 +01:00
Jason Dellaluce
226d1fb728 update(OWNERS): add jasondellaluce
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-22 18:15:40 +01:00
Lorenzo Susini
6319be8146 update(rules): Add containerd socket to sensitive_mount macro
Signed-off-by: Lorenzo Susini <susinilorenzo1@gmail.com>
2021-12-21 16:53:57 +01:00
Akos Kaldy
cf4672675c add Phoenix to adopters list
Signed-off-by: Akos Kaldy <kaldyka@gmail.com>
2021-12-20 17:44:12 +01:00
Angelo Puglisi
f035829ca2 fix(rules): typo in Create Symlink Over Sensitive Files rule output
Signed-off-by: Angelo Puglisi <angelopuglisi86@gmail.com>
2021-12-13 20:05:33 +01:00
Calvin Bui
cd471a78db re-add double empty newline
Signed-off-by: Calvin Bui <3604363+calvinbui@users.noreply.github.com>
2021-12-10 10:27:33 +01:00
Calvin Bui
65969c30f9 Add ECR repository to rules
Signed-off-by: Calvin Bui <3604363+calvinbui@users.noreply.github.com>
2021-12-10 10:27:33 +01:00
Federico Di Pierro
bb8b75a2cd update(userspace/falco): enforce check that content-type actually starts with "application/json" string.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-12-09 21:04:47 +01:00
Federico Di Pierro
b359f71511 fix(userspace/falco): accept 'Content-Type' header that contains "application/json", but it is not strictly equal to it.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-12-09 21:04:47 +01:00
Federico Di Pierro
9dcd8bccac fix(userspace/falco): in case output_file cannot be opened, throw a falco exception.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-12-09 21:02:48 +01:00
Jason Dellaluce
b5667cab99 chore(test): remove unused files in test directory
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-09 10:36:45 +01:00
Jason Dellaluce
2a00a4d853 rules: adding support to openat2
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:12:14 +01:00
Jason Dellaluce
697d4427a7 chore(scripts): refine removal output messages
Signed-off-by: Jason Dellaluce jasondellaluce@gmail.com
Co-authored-by: Leonardo Grasso me@leonardograsso.com
2021-12-06 19:09:14 +01:00
Jason Dellaluce
bf04fed71c fix(scripts): correctly remove loaded drivers
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-12-06 19:09:14 +01:00
Jason Dellaluce
c005af22cc fix: set config value and create node if not existing
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
c93029ce74 fix(build): use correct libyaml variable in tests cmake
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
076aabcea6 test(falco): adding unit tests for yaml_configuration
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
d8c588becf update: add yaml-cpp to unit tests
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
1a7611a761 chore(engine): using is_defined config method instead of private get_node
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
7fb61ba4a3 refactor(engine): access config fields with new key syntax
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
9ab810f431 update(engine): support accessing nested config fields
Since now, the maximum depth supported to access config fields is two.
This adds support for accessing fields of arbitrary nesting depth.
A formal grammar has been explicited for the regular language representing
the field keys. The accessor methods have been updated accordingly.

Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Jason Dellaluce
7781385769 refactor(engine): support string config loading and add ad-hoc methods
This is a change of direction from the current design, that imposes loading
the configuration from file only, and in the object constructor. Instead,
yaml_configuration objects can now be reused ad can load the YAML config
from either file or string. This also makes it easier to unit test this class.

Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-12-06 19:04:15 +01:00
Erick Cheng
205a8fd23b Move wget and curl to own rule
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
bdba37a790 Fix remove scp and add curl
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
19fb3458ef Add wget and curl to remote_file_copy_binaries
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
b0565794f5 Move user_known_ingress_remote_file_copy_activities to outside condition
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
66df790b9d Fix syntax error
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
749d4b4512 Add more curl download checks
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
851033c5f4 Add curl macro
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
af6f3bfeab Move wget and curl to own rule
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
c4d25b1d24 Fix remove scp and add curl
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Erick Cheng
d434853d5f Add wget and curl to remote_file_copy_binaries
Signed-off-by: Erick Cheng <19863605+ec4n6@users.noreply.github.com>
2021-11-29 17:42:40 +01:00
Jason Dellaluce
4c8e369691 update(build): bump fakeit version
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-22 18:25:44 +01:00
Jason Dellaluce
b15a0458b7 update(build): allow using local libs source dir
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
Co-authored-by: Federico Di Pierro <nierro92@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-18 16:26:18 +01:00
Jason Dellaluce
d6cb8bc4bd refactor(build): setting variable defaults according to newest libs version
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
Co-authored-by: Federico Di Pierro <nierro92@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-18 16:26:18 +01:00
Jason Dellaluce
2cc7fd9072 update(build): bump libs version
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
Co-authored-by: Federico Di Pierro <nierro92@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-18 16:26:18 +01:00
Jason Dellaluce
589829ae2f update(build): remove libscap patch
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
Co-authored-by: Federico Di Pierro <nierro92@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-18 16:26:18 +01:00
Jason Dellaluce
85db078dc4 chore: renaming comment references
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
Co-authored-by: Federico Di Pierro <nierro92@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-18 16:26:18 +01:00
sai-arigeli
23706da75e Allow append of new exceptions to rules
Signed-off-by: Sai Arigeli <saiharisharigeli@gmail.com>

Return warnings after validation of rule exceptions

Signed-off-by: Sai Arigeli <saiharisharigeli@gmail.com>

Update FALCO_ENGINE_VERSION

Signed-off-by: Sai Arigeli <saiharisharigeli@gmail.com>
2021-11-18 09:11:20 +01:00
Federico Di Pierro
35302f6f09 update(build): update libs to falcosecurity/libs master.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 17:25:24 +01:00
Federico Di Pierro
375a6f66c5 update(build): force using libs-bundled luajit.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-17 17:25:24 +01:00
Federico Di Pierro
e8a243d6ea wip: point to my own library for CI purposes.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 17:25:24 +01:00
Federico Di Pierro
7927f45d9f update(build): dropped Falco local luajit module, use the one provided by libs (upgraded) instead.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-17 17:25:24 +01:00
Federico Di Pierro
d9aff8d564 update(build): switched back to falcosecurity libs on master.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
40e3fdd09c update(build): updated libs.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
ba2323046a fix(build): properly use correct lib/lib64 folder for CIVETWEB_LIB variables.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
5e6f30109e update(build): dropped civetweb patch. Use different ExternalProject_Add when building with bundled openssl or not, to avoid depending on an unexhistent target.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
f3c3de7e05 fix(build): properly share OPENSSL_INCLUDE_DIR and OPENSSL_LIBRARIES vars to civetweb cmake.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
ca61f87682 update(build): civetweb depends on openssl.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
113bb5cdd6 update(build): update falcosecurity libs to use my own libs repo and version to be able to test the build against FedeDP:fix_ssl_1_1_get_all_data branch (not yet merged).
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
8a603c3c5d update(build): latest libs correctly set OPENSSL_LIBRARIES for us.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
0539e948c8 update(build): moved civetweb to its own cmake module. Moved its patch too.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
5f1d04ec82 fix(build): build civetweb using cmake and linking to static openssl built by us.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-17 16:18:23 +01:00
Federico Di Pierro
9d8fc4c8d2 update(build): updated civetweb to version 1.15 to correctly support openssl1.1.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-11-17 16:18:23 +01:00
Luca Guerra
09799e125d chore(build): update libs version to 7906f7ec416a8b67b82d92d37b25f28d545bcb8f
Signed-off-by: Luca Guerra <luca@guerra.sh>
2021-11-16 19:02:21 +01:00
Jason Dellaluce
446c65007d test(userspace/engine): add integration test for rules enabled with enabled flag only
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-16 16:37:45 +01:00
Jason Dellaluce
df3728ec3f test(userspace/engine): add integration test for rules disabled with enabled flag only
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-16 16:37:45 +01:00
Jason Dellaluce
a66dda3daa test(userspace/engine): update integration tests to support enabled-only rules
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-16 16:37:45 +01:00
Jason Dellaluce
eec2f5062f update(userspace/engine): allow overwriting rules with enabled flag only
This allows defining rules that simply enable/disable already defined rules, like the following:
- rule: A rule enabled by default
  enabled: false
- rule: A rule disabled by default
  enabled: true

Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-16 16:37:45 +01:00
Federico Di Pierro
7dcf8f4bf7 update(userspace/engine): use s_ prefix for static var.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>

Co-authored-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-16 15:34:12 +01:00
Federico Di Pierro
bea91ca844 fix(userspace/engine): properly constify m_default_ruleset.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-16 15:34:12 +01:00
Federico Di Pierro
ea2ca56d5b style(userspace/engine): avoid creating multiple versions of methods only to assume default ruleset. Use a default argument instead.
Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
2021-11-16 15:34:12 +01:00
Mark Stemm
cb51522423 Skip plugins list/load/tests for MUSL_OPTIMIZED_BUILD
When MUSL_OPTIMIZED_BUILD is specified, falco is statically linked under
musl, and can't dlopen() files: see
https://inbox.vuxu.org/musl/20200423162406.GV11469@brightrain.aerifal.cx/T/

So skip listing/loading/testing plugins when MUSL_OPTIMIZED_BUILD is specified.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
9f53089bcb Detect strlcpy on the fly (musl libc)
Detect strlcpy on the fly, as was done in https://github.com/falcosecurity/libs/pull/110.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
2a4e4d555d Add automated tests for plugins
Test infrastructure and sample confs/rules/traces for plugins
automated tests:

New test cases are in falco_tests_plugins.yaml and cover:
- Listing plugins and fields when plugins are loaded.
- Basic cloudtrail + json plugin on a fake cloudtrail json file and a
  sample rule that uses both plugins.
- Conflicts between source/extractor plugins
- Incompatible plugin api
- Wrong plugin path
- Checking for warnings when reading rules with unnown sources (e.g. when plugins are not loaded)

Some test-only plugins written in C are in test/plugins and built on
the fly. (They aren't included in packages of course).

The test framework needed some small changes to handle these tests:
- Add a mode to not check detection counts at all (for --list/--list-plugins)
- addl_cmdline_opts to allow specifying --list/--list-plugins
- Using DOTALL when matching stderr/stdout (allows multi-line matches more easily)

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
6a1f4f7374 Plugins support in falco executable
Update the falco binary to add support for plugins.

- Keep track of an "event source", which is initially "syscall" but
  changes to the input plugin's source if an source plugin ends up being
  loaded.

- New argument --list-plugins will return info on any loaded plugins,
  using sinsp_plugin::plugin_infos.

- Create filter/formatter factories for plugins. This ensures that
  filterchecks for syscalls are not used for plugins and vice versa.

- Use sinsp_plugin::register_plugin() to load each plugin found in
  config. The first source plugin found (if any) calls
  engine->add_source withthe source plugin's event source.

- If a second source plugin is found, exit with an error.

- Extractor plugins must be compatible with the event source (usually
  the plugin event source, but could be "syscall"). If not, exit with
  an error.

- Multiple Extractor plugins are allowed, but they can not have
  overlapping compatible event sources. This is mostly to avoid
  confusion, but we might change this later.

- After loading plugins, use engine is_plugin_compatible to ensure
  that the plugin is compatible with any required_plugin_version blocks
  in falco rules.

- Normally falco would log warnings if too many SCAP_TIMEOUT results
  were received. These are more expected when using plugins, so only
  log these warnings when using syscalls.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
98599d5e25 Plugins support (outputs)
The generic events support already handled most of this, with a
dedicated formatter factory for plugin sources. Just one missing
header include and change the logic slightly for json parsing.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
e7d41f8166 Rules loading changes for plugins
Rules loading changes for plugins:

 - parse required_engine_versions from yaml and pass up to rules
   loader as a lua table as an additional return value from load_rules().
 - c++ rules loader converts to map: plugin -> list of required plugin
   versions
 - support is_source_valid callback from lua, calls engine method. If
   a source is not valid, skip any rules for that source and add a warning.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
9075eea62f Falco engine support for plugins
Mostly plugins are just handled as a new filter/formatter factory with
a new source based on the loaded input plugin, but there are a few
changes at the engine level:

- is_source_valid returns whether a filter/formatter factory exists
  for a given source. Will be used by rules loaded to skip rules for
  an unknown source.

- the falco engine now holds the required_plugin_version predicates
  found in rules files and a method is_plugin_compatible returns whether
  a plugin semver is compatible with the predicates in the rules

- Update the falco engine version and fields checksum for plugins

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
69e32f7ed1 Add initial set of Cloudtrail rules
These rules can be used when combined with the cloudtrail plugin.

They're installed to /etc/falco like the other rules files.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
38a7f7ada0 cmake/build changes for plugins
Add a cmake module "plugins" that does the following:

 - Downloads/installs the plugins artifacts from a known tag
 - Copies the resulting cloudtrail/json shared libraries to
   CMAKE_CURRENT_BINARY_DIR/plugins
 - Installs them to FALCO_SHARE_DIR/plugins

The default config will define the plugins but they will be disabled
by default.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
6a4e4eaa4f Finish moving token bucket impl from falco to libs
It took a while, but we remembered to finish moving the token_bucket
from falco engine to libs. There were 2 copies for a while.

This brings over one change to libs--to have an optional timer
function.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Mark Stemm
1313e77113 Falco yaml config for plugins
Update config code/default falco.yaml to add support for plugins:

- Update config parsing methods to support reading plugin config
  objects in a list from yaml.

- The default config defines the cloudtrail/json plugins but does not
  give them any actual config for init config/open
  params (cloudtrail), or init config (json).

- load_plugins is empty so neither plugin is actually loaded by default.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>-
2021-11-12 18:27:59 +01:00
Mark Stemm
a1fa8edf7e Update falcosecurity/libs version
This moves up the commit to one that has plugins support.

Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
Co-authored-by: Loris Degioanni <loris@sysdig.com>
Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-12 18:27:59 +01:00
Dominic Evans
d4aa7b9747 build: always populate partial version variables
When FALCO_VERSION was provided via a CMake variable, the build would
eventually fail because the partial version variables hadn't been
populated. Move the creation of those outside the check of FALCO_VERSION
being set so they also happen when that is provided too.

Contributes-to: #1654

Signed-off-by: Dominic Evans <dominic.evans@uk.ibm.com>
2021-11-12 17:19:24 +01:00
Manuel Gauto
2312afe9cd Set digest-algo for gpg to use SHA256 for linux packages.
Signed-off-by: Manuel Gauto <mgauto@mgenterprises.org>
2021-11-12 17:17:27 +01:00
Jason Dellaluce
6ee0b353ac test: adding a test for correct json pointer parsing
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-11 18:36:21 +01:00
Jason Dellaluce
28d6a293fc update(userspace/engine): support jsonpointer escaping in rule parser
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-11-11 18:36:21 +01:00
Hitesh Sharma
5ee62f66f7 adding raft in the adopters list
Signed-off-by: Hitesh Sharma <33040859+teshsharma@users.noreply.github.com>
2021-11-10 16:16:40 +01:00
Mark Stemm
b33fb6052a Update ubuntu image for driver-loader tests (20.04LTS)
Update the ubuntu image for driver-loader/integration tests from
16.04LTS to 20.04LTS.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-11-03 16:49:11 +01:00
David Windsor
8448d02980 falco-driver-loader: don't fail if chcon is missing in load_kernel_module()
Signed-off-by: David Windsor <dwindsor@secureworks.com>
2021-11-02 16:49:55 +01:00
David Windsor
74661a7d8f Apply suggestions from code review
Don't fail if chcon is not present

Co-authored-by: Leo Di Donato <leodidonato@gmail.com>
Signed-off-by: David Windsor <dwindsor@secureworks.com>
2021-11-02 16:49:55 +01:00
David Windsor
e7b320b00c Fix falco-driver-loader SELinux insmod denials
Signed-off-by: David Windsor <dwindsor@secureworks.com>
2021-11-02 16:49:55 +01:00
Sverre Boschman
762500a361 add known k8s service accounts
Signed-off-by: Sverre Boschman <1142569+sboschman@users.noreply.github.com>
2021-10-29 10:41:54 +02:00
Sverre Boschman
8563af8a79 reformat known_sa_list
Signed-off-by: Sverre Boschman <1142569+sboschman@users.noreply.github.com>
2021-10-29 10:41:54 +02:00
Mark Stemm
f7893fbd14 Change expected result for old trace file with old execve event num
The trace file traces-positive/run-shell-untrusted.scap has an old
execve event number (PPME_SYSCALL_EXECVE_18), which was replaced by
PPME_SYSCALL_EXECVE_19 in 2018.

Given the changes in https://github.com/falcosecurity/libs/pull/94,
these events are now skipped. So change the test to note that *no*
events will be detected.

As a bit of context, event numbers won't be changing any longer--a
change around the same time 298fbde8029020ce3fbddd07e2910b59cc402b8b
allowed for extending existing events to add new parameters instead of
having to define a new event number just to add a new parameter. So
the notion of "old events" should not exist for any event created
after mid-to-late 2018.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
3b390793b9 Fix bug in macro that was masked by old evttype checking
It turns out that the macro inbound_outbound had a logical bug where
joining the beginning and end of the macro with "or" led to the macro
matching all event types by accident.

Most of the time this isn't harmful but it turns out some trace files
will do operations on inet connection fds like "dup", and those get
mistakenly picked up by this macro, as the fd for the event does
happen to be a network connection fd.

This fixes the macro to only match those event types *and* when the fd
is a inet connection fd.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
10d47cb1f5 Update automated tests to reflect evttypes behavior
With the changes in https://github.com/falcosecurity/libs/pull/74,
there isn't any need to warn about the order of operators and the
evt.type field--the set of event types for a filter should be exact
now regardless of the order of operators.

So update tests that were logging those warnings to note that the
warnings won't occur any more.

Also, some tests more accurately *do* note that they have an overly
permissive evttype (e.g. ones related to syscalls, which are uncommon
and are evaluated for all event types) to reflect the new behavior.

Finally, in unit tests create an actual sinsp filter instead of a
gen_event_filter, which is the base class and shouldn't be created
directly.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
204892816b Update falco engine checksum
This makes the output of --list a bit more precise to only include
filter fields and not output fields.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
6156fbb4cb Update falcosecurity-libs cmake revision
This has recent changes to support more general purpose event
formatting.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
20b5ea8f85 Check for ignored syscall event types after loading rules
This step used to be done in the lua rule loading code, but now we can
get it directly from the filters, so do it in falco instead.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
cc43c721c9 Add a default ruleset version of evttypes_for_ruleset
This allows for working with the default ruleset like other methods.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
230c22b674 Update lua rule loading to reflect other changes
Update the lua side of rule loading to reflect other changes:

- install_filter renamed to create_filter_obj, and takes just a
  lua_parser object created via falco_rules.create_lua_parser() and
  uses a single lua callback "filter" instead of separate ones for
  syscall/k8s_audit. It can return an error, including about
  undefined fields

- is_defined_filter, which used to be local and based on the result of
  sinsp_rule_utils.check_for_ignored_syscalls_events, is now a
  lua_callback falco_rules.is_defined_field().

- Don't need to pass down sinsp_lua_parser/json_lua_parser now,
  creating filters is handled via lua callbacks.

- Checking for ignored syscalls/events is now done in falco itself,
  after loading rules.

- add_xxx_filter replaced by add_filter + source.

- Use is_format_valid instead of formats.formatter/formats.free_formatter.

- We don't need the functions in sinsp_rule_utils any longer, so
  remove the file and don't import it.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
04f3cc503c Add ability to check if field is defined
Add a function is_defined_field(source, fldname) that returns whether
a field with name fldname exists for the given event source. This uses
the filter factory to create a filtercheck, and returns true if an
object was created.

This prevents having to push down the entire set of defined fields
before calling load_rules().

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
84d7020e3e Use the new falco engine interface w/ generic events
Use the new falco engine interface with support for generic events
instead of event-specific process_xxx_event methods.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
0cae713412 Use new outputs interface with engine
Use the new outputs interface, that uses the engine to provide a
formatter.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
bbbac6203c Update rules loader to be general purpose, through factories
Update rules loader to be more general purpose by using factories and
the general purpose engine:

- A lua callback create_lua_parser creates a lua_parser with a filter
  object of the right type. The lua parser can then iterate the AST
  and populate the filter object.

- Like the falco engine, the rules loader is configured with a list of
  factories, and add_filter is now general purpose, taking a source.

Given the fix in https://github.com/falcosecurity/libs/pull/72, there
isn't any need to pass down the entire set of sinsp event
types/syscalls and validate that all filter event types are
valid. That job is now handled by the sinsp filter parsing
code. add_filter now returns the number of event types used by the new
filter, and if that number is excessive the lua code will return a
warning.

Format handling is mostly not handled by the rules loader any more. As
a convienence, there's a new lua callback is_format_valid which takes
a source and output string and uses the right formatter factory to
create a formatter. As long as that doesn't throw an exception, the
format is valid.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
8275730bf8 Use factories to provide filters/formatting
Instead of having hard-coded support for syscall/k8s_audit events, use
the notions of filter factories/formatter factories to provide generic
support for events having a given source:

- Within the engine, maps m_filter_factories / m_rulesets /
  m_format_factories map from a given source to something that can
  create filters, hold filters, and create formatters for a given
  source. The hard-coded sinsp_factory/json_factory objects are removed.

- The specific add_xxx_filter/process_xxx_event are general purpose
  and take an event source.

- A new method create_formatter() takes a source/output format and
  provides a shared_ptr to a formatter than can resolve format
  strings. This is used by the falco outputs code.

- In falco main, create the syscall/k8s_audit filter and formatter
  factories and pass them to the engine. Later, we might make this
  configurable/selective.

With all of the above changes, the falco engine doesn't need a direct
inspector any longer, so remove it.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
de4b2fa831 Make json_event_formatter a gen_event_formatter
Make json_event_formatter a generic event formatter by inheriting from
gen_event_formatter and implementing its methods.

Most of the actual work is still done by resolve_format (previously
resolve_tokens, to avoid confusion with sinsp formatter, as it behaves
slightly differently).

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
943a37fcf7 General-purpose list_fields(), via factories
Take advantage of the changes in
https://github.com/falcosecurity/libs/pull/75 to have a
general-purpose way to list fields for a given event source.

in the engine, list_fields() now takes a source, iterates over filter
factories, and calls get_fields() for each factory, printing the results.

list_source_fields now calls the engine regardless of source.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
3202921355 falco_formats only formats events now, no lua bindings
Modify falco_formats to only be responsible for resolving a rule's
output string or coming up with a map of field name->field values from
a given output string.

It relies on the changes in
https://github.com/falcosecurity/libs/pull/77 to use generic
formatters for a given source.

Remove lua bindings to create a formatter/free a formatter. Those were
unused as of the changes in
https://github.com/falcosecurity/falco/pull/1451, so finally remove
them now.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
1c60dab87e Move json -> k8s audit event conversion out of falco engine
Move the code that splits a json object into a list of k8s audit/json
events out of falco engine and into json_evt.

This, along with other changes, allows the falco engine to be more
general purpose and not directly tied to the notion of syscall vs k8s
audit events.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Mark Stemm
044a7c153e Don't track event "tags" i.e. event types in rulesets
Modify rulesets to not keep track of the event types for a given set
filter. Instead, using the changes in
https://github.com/falcosecurity/libs/pull/74 event types are returned
directly by the filter.

Within each ruleset, there's a vector that maps from event number to
set of filters that are related to that event number. There's also a
general set of filters for all event types.

run() both indexes into the per-event vector as well as iterate over
the all event types set.

Also, used shared_ptr instead of direct pointers, which matches the
updated interface used by lua_parser. This simplifies the bookkeeping
a bit (no more delete when removing rulesets).

Given these changes, there's no need for a separate
falco_sinsp_ruleset class any longer, so remove it.

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2021-10-12 17:59:38 +02:00
Frederico Araujo
a0f7d7cf85 update(adopters.md): add falco libs users section
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
2021-10-07 12:32:12 +02:00
Frederico Araujo
bb81133201 docs(changelog.md): update for release 0.30.0
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
2021-09-30 17:20:15 +02:00
Michele Zuccala
46d5266ac8 build(cmake): bump libs version to 3aa7a83
Signed-off-by: Michele Zuccala <michele@zuccala.com>
2021-09-29 19:50:14 +02:00
Leo Di Donato
3414ca5361 update(proposal): clarify that old drivers are not removed anymore
Signed-off-by: Leonardo Di Donato <leodidonato@gmail.com>
2021-09-29 16:51:25 +02:00
Jason Dellaluce
0eb170cf5f update(test): enhance test cases for tags in json outputs
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-28 12:44:27 +02:00
Jason Dellaluce
21fa6e9505 update(outputs): make tags configurable in json output
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-28 12:44:27 +02:00
Michele Zuccala
b82cbb1b59 build(cmake): bump libs version to 5727c45
Signed-off-by: Michele Zuccala <michele@zuccala.com>
2021-09-24 17:58:22 +02:00
Domenico Chirabino
d033868ab9 falso.service: set StandardOutput to null
Signed-off-by: Domenico Chirabino <chirabino@protonmail.com>
2021-09-23 08:46:47 +02:00
Jason Dellaluce
7c98d0047c update(outputs): fixing spacing issue
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:59:10 +02:00
Jason Dellaluce
c7d9b6ee7f test(outputs): add source and tags to json output
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:59:10 +02:00
Jason Dellaluce
8273e57598 new(outputs): add source and tags to json output
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:59:10 +02:00
Jason Dellaluce
b0562242e8 test(grpc): Test tags on outputs service
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:59:10 +02:00
Jason Dellaluce
ca66b84e5a new(grpc): Add tags to outputs service
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:59:10 +02:00
spartan
7c9ec9fc17 fix bugs
Signed-off-by: Spartan-65 <liuyanchong@outlook.com>
2021-09-21 18:54:09 +02:00
Jason Dellaluce
9ea43c2663 update(test): check output order in output_strictly_contains
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:53:09 +02:00
Jason Dellaluce
4d55847bd4 fix(test): avoid output_strictly_contains failures
Signed-off-by: Jason Dellaluce <jasondellaluce@gmail.com>
2021-09-21 18:53:09 +02:00
Michele Zuccala
a684bec007 update(userspace/falco): throw logic errors on invalid config values for metadata download
Signed-off-by: Michele Zuccala <michele@zuccala.com>
2021-09-20 16:56:15 +02:00
Michele Zuccala
812aa9b566 new(userspace/falco): add customizable metadata fetching params
Signed-off-by: Michele Zuccala <michele@zuccala.com>
2021-09-20 16:56:15 +02:00
Tom Keyte
e0f8b81692 Remove duplicate allowed ecr registry rule
Signed-off-by: Tom Keyte <tom.keyte@onsecurity.co.uk>
2021-09-17 11:12:54 +02:00
Alberto Pellitteri
874809351f rules(list https_miner_domains): fix typo in the list
Co-authored-by: darryk10 <stefano.chierici@sysdig.com>
Signed-off-by: Alberto Pellitteri <albertopellitteri96@gmail.com>
2021-09-17 09:16:54 +02:00
Alberto Pellitteri
4527228ef8 rules(list https_miner_domains): add new miner domains
Signed-off-by: Alberto Pellitteri <albertopellitteri96@gmail.com>
Co-authored-by: darryk10 <stefano.chierici@sysdig.com>
2021-09-17 09:16:54 +02:00
Alberto Pellitteri
e684c95e23 rules(list miner_domains): add new miner domains
Signed-off-by: Alberto Pellitteri <albertopellitteri96@gmail.com>
Co-authored-by: darryk10 <stefano.chierici@sysdig.com>
2021-09-17 09:16:54 +02:00
Leonardo Grasso
2390ca447a new: ability to filter by a node when fetching K8S metadata
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2021-09-16 15:33:41 +02:00
Leonardo Grasso
af0e6da375 build(cmake/modules): upgrade driver version to f7029e
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2021-09-16 15:33:41 +02:00
Michal Schott
84e7d3f18f Switching from stable to old-stable (buster).
Added libssl-dev package.

Signed-off-by: Michal Schott <michal.schott@onegini.com>
2021-09-10 01:11:38 +02:00
Thomas Labarussias
2a8c0e8bb7 add Qonto as adopter
Signed-off-by: Thomas Labarussias <issif+github@gadz.org>
2021-09-02 17:36:36 +02:00
Michele Zuccala
f28688551c fix(build): adapt to new debian 11 package names
Signed-off-by: Michele Zuccala <michele@zuccala.com>
2021-08-25 17:18:20 +02:00
Leonardo Grasso
b12d37a3b8 docs(RELEASE.md): switch to 3 releases per year
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2021-08-25 17:17:20 +02:00
Loris Degioanni
5e027c7fe2 Proposal for a libs plugin system
Description of changes to falcosecurity/libs and /falco to support
plugins to provide events and extract fields from events.

Signed-off-by: Loris Degioanni <loris@sysdig.com>
Co-authored-by: Leonardo Di Donato <leodidonato@gmail.com>
Co-authored-by: Mark Stemm <mark.stemm@gmail.com>
Co-authored-by: Leonardo Grasso <me@leonardograsso.com>
2021-08-24 17:52:19 +02:00
Leo Di Donato
efbe887d6e docs: CHANGELOG for 0.29.1 cleanup
Signed-off-by: Leonardo Di Donato <leodidonato@gmail.com>
2021-07-30 12:20:10 +02:00
Leonardo Grasso
7dcbeb1f44 build(.circleci): ncurses is not required anymore
Since `libs` version 13ec67ebd23417273275296813066e07cb85bc91

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2021-07-29 18:20:47 +02:00
Leonardo Grasso
93667f2d3e build(docker/builder): ncurses-dev is not required anymore
Since `libs` version 13ec67ebd23417273275296813066e07cb85bc91

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2021-07-29 18:20:47 +02:00
113 changed files with 4622 additions and 2978 deletions

View File

@@ -13,7 +13,7 @@ jobs:
command: apk update
- run:
name: Install build dependencies
command: apk add g++ gcc cmake cmake make ncurses-dev git bash perl linux-headers autoconf automake m4 libtool elfutils-dev libelf-static patch binutils
command: apk add g++ gcc cmake cmake make git bash perl linux-headers autoconf automake m4 libtool elfutils-dev libelf-static patch binutils
- run:
name: Prepare project
command: |
@@ -60,7 +60,7 @@ jobs:
command: apt update -y
- run:
name: Install dependencies
command: DEBIAN_FRONTEND=noninteractive apt install libjq-dev libncurses-dev libyaml-cpp-dev libelf-dev cmake build-essential git -y
command: DEBIAN_FRONTEND=noninteractive apt install libjq-dev libyaml-cpp-dev libelf-dev cmake build-essential git -y
- run:
name: Prepare project
command: |
@@ -92,7 +92,7 @@ jobs:
command: apt update -y
- run:
name: Install dependencies
command: DEBIAN_FRONTEND=noninteractive apt install libssl-dev libyaml-dev libncurses-dev libc-ares-dev libprotobuf-dev protobuf-compiler libjq-dev libyaml-cpp-dev libgrpc++-dev protobuf-compiler-grpc rpm libelf-dev cmake build-essential libcurl4-openssl-dev linux-headers-generic clang llvm git -y
command: DEBIAN_FRONTEND=noninteractive apt install libssl-dev libyaml-dev libc-ares-dev libprotobuf-dev protobuf-compiler libjq-dev libyaml-cpp-dev libgrpc++-dev protobuf-compiler-grpc rpm libelf-dev cmake build-essential libcurl4-openssl-dev linux-headers-generic clang llvm git -y
- run:
name: Prepare project
command: |
@@ -124,7 +124,7 @@ jobs:
command: apt update -y
- run:
name: Install dependencies
command: DEBIAN_FRONTEND=noninteractive apt install libssl-dev libyaml-dev libncurses-dev libc-ares-dev libprotobuf-dev protobuf-compiler libjq-dev libyaml-cpp-dev libgrpc++-dev protobuf-compiler-grpc rpm libelf-dev cmake build-essential libcurl4-openssl-dev linux-headers-generic clang llvm git -y
command: DEBIAN_FRONTEND=noninteractive apt install libssl-dev libyaml-dev libc-ares-dev libprotobuf-dev protobuf-compiler libjq-dev libyaml-cpp-dev libgrpc++-dev protobuf-compiler-grpc rpm libelf-dev cmake build-essential libcurl4-openssl-dev linux-headers-generic clang llvm git -y
- run:
name: Prepare project
command: |
@@ -156,7 +156,7 @@ jobs:
command: apt update -y
- run:
name: Install dependencies
command: DEBIAN_FRONTEND=noninteractive apt install cmake build-essential clang llvm git linux-headers-generic libncurses-dev pkg-config autoconf libtool libelf-dev -y
command: DEBIAN_FRONTEND=noninteractive apt install cmake build-essential clang llvm git linux-headers-generic pkg-config autoconf libtool libelf-dev -y
- run:
name: Prepare project
command: |
@@ -188,7 +188,7 @@ jobs:
command: dnf update -y
- run:
name: Install dependencies
command: dnf install gcc gcc-c++ git make cmake autoconf automake pkg-config patch ncurses-devel libtool elfutils-libelf-devel diffutils kernel-devel kernel-headers kernel-core clang llvm which -y
command: dnf install gcc gcc-c++ git make cmake autoconf automake pkg-config patch libtool elfutils-libelf-devel diffutils kernel-devel kernel-headers kernel-core clang llvm which -y
- run:
name: Prepare project
command: |
@@ -292,6 +292,7 @@ jobs:
BUILD_DIR: "/build-static"
BUILD_TYPE: "release"
SKIP_PACKAGES_TESTS: "true"
SKIP_PLUGINS_TESTS: "true"
steps:
- setup_remote_docker
- attach_workspace:
@@ -303,7 +304,7 @@ jobs:
path: /build-static/release/integration-tests-xunit
"tests/driver-loader/integration":
machine:
image: ubuntu-1604:202004-01
image: ubuntu-2004:202107-02
steps:
- attach_workspace:
at: /tmp/ws
@@ -403,7 +404,7 @@ jobs:
name: Setup
command: |
apt update -y
apt-get install apt-utils bzip2 gpg python python-pip -y
apt-get install apt-utils bzip2 gpg python python3-pip -y
pip install awscli
echo $GPG_KEY | base64 -d | gpg --import
- run:
@@ -517,7 +518,7 @@ jobs:
name: Setup
command: |
apt update -y
apt-get install apt-utils bzip2 gpg python python-pip -y
apt-get install apt-utils bzip2 gpg python python3-pip -y
pip install awscli
echo $GPG_KEY | base64 -d | gpg --import
- run:

View File

@@ -5,7 +5,7 @@ Known end users with notable contributions to the project include:
* IBM
* Red Hat
Falco is being used by numerous other companies, both large and small, to build higher layer products and services. The list includes but is not limited to:
Falco is being used by numerous other companies, both large and small, to build higher layer products and services. The list includes but is not limited to:
* Equinix Metal
* IEEE
* Lowes
@@ -26,6 +26,8 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Frame.io](https://frame.io/) - Frame.io is a cloud-based (SaaS) video review and collaboration platform that enables users to securely upload source media, work-in-progress edits, dailies, and more into private workspaces where they can invite their team and clients to collaborate on projects. Understanding what is running on production servers, and the context around why things are running is even more tricky now that we have further abstractions like Docker and Kubernetes. To get this needed visibility into our system, we rely on Falco. Falco's ability to collect raw system calls such as open, connect, exec, along with their arguments offer key insights on what is happening on the production system and became the foundation of our intrusion detection and alerting system.
* [Giant Swarm](https://www.giantswarm.io/) - Giant Swarm manages Kubernetes clusters and infrastructure for enterprises across multiple cloud providers as well as several flavors of on-premises data centers. Our platform provisions and monitors pure "vanilla" Kubernetes clusters which can be augmented with managed solutions to many common Kubernetes challenges, including security. We use Falco for anomaly detection as part of our collection of entirely open-source tools for securing our own clusters, and offer the same capabilities to our customers as part of our [managed security offering](https://docs.giantswarm.io/app-platform/apps/security/).
* [GitLab](https://about.gitlab.com/direction/defend/container_host_security/) - GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate. GitLab Ultimate provides the single tool teams need to find, triage, and fix vulnerabilities in applications, services, and cloud-native environments enabling them to manage their risk. This provides them with repeatable, defensible processes that automate security and compliance policies. GitLab includes a tight integration with Falco, allowing users to defend their containerized applications from attacks while running in production.
* [League](https://league.com/ca/) - League provides health benefits management services to help employees understand and get the most from their benefits, and employers to provide effective, efficient plans. Falco is used to monitor our deployed services on Kubernetes, protecting against malicious access to containers which could lead to leaks of PHI or other sensitive data. The Falco alerts are logged in Stackdriver for grouping and further analysis. In the future, we're hoping for integrations with Prometheus and AlertManager as well.
@@ -40,9 +42,13 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Preferral](https://www.preferral.com) - Preferral is a HIPAA-compliant platform for Referral Management and Online Referral Forms. Preferral streamlines the referral process for patients, specialists and their referral partners. By automating the referral process, referring practices spend less time on the phone, manual efforts are eliminated, and patients get the right care from the right specialist. Preferral leverages Falco to provide a Host Intrusion Detection System to meet their HIPPA compliance requirements.
* https://hipaa.preferral.com/01-preferral_hipaa_compliance/
* [Qonto](https://qonto.com) - Qonto is a modern banking for SMEs and freelancers. Qonto provides a fully featured business account with a simplified accounting flow. Falco is used by our SecOps team to detect suspicous behaviors in our clusters.
* [Raft](https://goraft.tech) - Raft is a government contractor that offers cloud-native solutions across many different agencies including DoD (Department of Defense), HHS (Health and Human Services), as well as within CFPB (Consumer Finance Protection Bureau). Raft leverages Falco to detect threats in our client's Kubernetes clusters and as a Host Intrusion Detection System. Raft proudly recommends Falco across all our different projects.
* [Replicated](https://www.replicated.com/) - Replicated is the modern way to ship on-prem software. Replicated gives software vendors a container-based platform for easily deploying cloud native applications inside customers' environments to provide greater security and control. Replicated uses Falco as runtime security to detect threats in the Kubernetes clusters which host our critical SaaS services.
* [Secureworks](https://www.secureworks.com/) - Secureworks is a leading worldwide cybersecurity company with a cloud-native security product that combines the power of human intellect with security analytics to unify detection and response across cloud, network, and endpoint environments for improved security operations and outcomes. Our Taegis XDR platform and detection system processes petabytes of security relevant data to expose active threats amongst the billions of daily events from our customers. We are proud to protect our platforms Kubernetes deployments, as well as help our customers protect their own Linux and container environments, using Falco.
* [Secureworks](https://www.secureworks.com/) - Secureworks is a leading worldwide cybersecurity company with a cloud-native security product that combines the power of human intellect with security analytics to unify detection and response across cloud, network, and endpoint environments for improved security operations and outcomes. Our Taegis XDR platform and detection system processes petabytes of security relevant data to expose active threats amongst the billions of daily events from our customers. We are proud to protect our platforms Kubernetes deployments, as well as help our customers protect their own Linux and container environments, using Falco.
* [Shopify](https://www.shopify.com) - Shopify is the leading multi-channel commerce platform. Merchants use Shopify to design, set up, and manage their stores across multiple sales channels, including mobile, web, social media, marketplaces, brick-and-mortar locations, and pop-up shops. The platform also provides merchants with a powerful back-office and a single view of their business, from payments to shipping. The Shopify platform was engineered for reliability and scale, making enterprise-level technology available to businesses of all sizes. Shopify uses Falco to complement its Host and Network Intrusion Detection Systems.
@@ -54,12 +60,18 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Swissblock Technologies](https://swissblock.net/) At Swissblock we connect the dots by combining cutting-edge algorithmic trading strategies with in-depth market analysis. We route all Falco events to our control systems, both monitoring and logging. Being able to deeply analyse alerts, we can understand what is running on our Kubernetes clusters and check against security policies, specifically defined for each workload. A set of alarms notifies us in case of critical events, letting us react fast. In the near future we plan to build a little application to route Kubernetes internal events directly to Falco, fully leveraging Falco PodSecurityPolicies analyses.
* [Shapesecurity/F5] (https://www.shapesecurity.com/) Shapesecurity defends against application fraud attacks like Account Take Over, Credential Stuffing, Fake Accounts, etc. Required by FedRamp certification, we needed to find a FIM solution to help monitor and protect our Kubernetes clusters. Traditional FIM solutions were not scalable and not working for our environment, but with Falco we found the solution we needed. Falco's detection capabilities have helped us identify anomalous behaviour within our clusters. We leverage Sidekick (https://github.com/falcosecurity/charts/tree/master/falcosidekick) to send Falco alerts to a PubSub which in turn publishes those alerts to our SIEM (SumoLogic)
* [Shapesecurity/F5](https://www.shapesecurity.com/) Shapesecurity defends against application fraud attacks like Account Take Over, Credential Stuffing, Fake Accounts, etc. Required by FedRamp certification, we needed to find a FIM solution to help monitor and protect our Kubernetes clusters. Traditional FIM solutions were not scalable and not working for our environment, but with Falco we found the solution we needed. Falco's detection capabilities have helped us identify anomalous behaviour within our clusters. We leverage Sidekick (https://github.com/falcosecurity/charts/tree/master/falcosidekick) to send Falco alerts to a PubSub which in turn publishes those alerts to our SIEM (SumoLogic)
* [Yahoo! JAPAN](https://www.yahoo.co.jp/) Yahoo! JAPAN is a leading company of internet in Japan. We build an AI Platform in our private cloud and provide it to scientists in our company. AI Platform is a multi-tenant Kubernetes environment and more flexible, faster, more efficient Machine Learning environment. Falco is used to detect unauthorized commands and malicious access and our AI Platform is monitored and alerted by Falco.
* [Sysdig](https://www.sysdig.com/) Sysdig originally created Falco in 2016 to detect unexpected or suspicious activity using a rules engine on top of the data that comes from the sysdig kernel system call probe. Sysdig provides tooling to help with vulnerability management, compliance, detection, incident response and forensics in Cloud-native environments. Sysdig Secure has extended Falco to include: a rule library, the ability to update macros, lists & rules via the user interface and API, automated tuning of rules, and rule creation based on profiling known system behavior. On top of the basic Falco rules, Sysdig Secure implements the concept of a "Security policy" that can comprise several rules which are evaluated for a user-defined infrastructure scope like Kubernetes namespaces, OpenShift clusters, deployment workload, cloud regions etc.
## Projects that use Falco libs
* [R6/Phoenix](https://r6security.com/) is an attack surface protection company that uses moving target defense to provide fully automated, proactive and devops friendly security to its customers. There are a set of policies you can add to enable the moving target defense capabilities. Some of them are triggered by a combination of Falco's findings. You can kill, restart and rename pods according to the ever changing policies.
* [SysFlow](https://sysflow.io) SysFlow is a cloud-native system telemetry framework that focuses on data abstraction, behavioral analytics, and noise reduction. At its core, SysFlow exposes a compact open telemetry format that records workload behaviors by connecting event and flow representations of process control flows, file interactions, and network communications. The resulting abstraction encodes a graph structure that enables provenance reasoning on host and container environments, and fast retrieval of security-relevant information.
## Adding a name
If you would like to add your name to this file, submit a pull request with your change.
If you would like to add your name to this file, submit a pull request with your change.

View File

@@ -1,12 +1,51 @@
# Change Log
## v0.30.0
Released on 2021-10-01
### Major Changes
* new: add `--k8s-node` command-line options, which allows filtering by a node when requesting metadata of pods to the K8s API server [[#1671](https://github.com/falcosecurity/falco/pull/1671)] - [@leogr](https://github.com/leogr)
* new(outputs): expose rule tags and event source in gRPC and json outputs [[#1714](https://github.com/falcosecurity/falco/pull/1714)] - [@jasondellaluce](https://github.com/jasondellaluce)
* new(userspace/falco): add customizable metadata fetching params [[#1667](https://github.com/falcosecurity/falco/pull/1667)] - [@zuc](https://github.com/zuc)
### Minor Changes
* update: bump driver version to 3aa7a83bf7b9e6229a3824e3fd1f4452d1e95cb4 [[#1744](https://github.com/falcosecurity/falco/pull/1744)] - [@zuc](https://github.com/zuc)
* docs: clarify that previous Falco drivers will remain available at https://download.falco.org and no automated cleanup is run anymore [[#1738](https://github.com/falcosecurity/falco/pull/1738)] - [@leodido](https://github.com/leodido)
* update(outputs): add configuration option for tags in json outputs [[#1733](https://github.com/falcosecurity/falco/pull/1733)] - [@jasondellaluce](https://github.com/jasondellaluce)
### Bug Fixes
* fix(scripts): correct standard output redirection in systemd config (DEB and RPM packages) [[#1697](https://github.com/falcosecurity/falco/pull/1697)] - [@chirabino](https://github.com/chirabino)
* fix(scripts): correct lookup order when trying multiple `gcc` versions in the `falco-driver-loader` script [[#1716](https://github.com/falcosecurity/falco/pull/1716)] - [@Spartan-65](https://github.com/Spartan-65)
### Rule Changes
* rule(list miner_domains): add new miner domains [[#1729](https://github.com/falcosecurity/falco/pull/1729)] - [@AlbertoPellitteri](https://github.com/AlbertoPellitteri)
* rule(list https_miner_domains): add new miner domains [[#1729](https://github.com/falcosecurity/falco/pull/1729)] - [@AlbertoPellitteri](https://github.com/AlbertoPellitteri)
### Non user-facing changes
* add Qonto as adopter [[#1717](https://github.com/falcosecurity/falco/pull/1717)] - [@Issif](https://github.com/Issif)
* docs(proposals): proposal for a libs plugin system [[#1637](https://github.com/falcosecurity/falco/pull/1637)] - [@ldegio](https://github.com/ldegio)
* build: remove unused `ncurses` dependency [[#1658](https://github.com/falcosecurity/falco/pull/1658)] - [@leogr](https://github.com/leogr)
* build(.circleci): use new Debian 11 package names for python-pip [[#1712](https://github.com/falcosecurity/falco/pull/1712)] - [@zuc](https://github.com/zuc)
* build(docker): adding libssl-dev, upstream image reference pinned to `debian:buster` [[#1719](https://github.com/falcosecurity/falco/pull/1719)] - [@michalschott](https://github.com/michalschott)
* fix(test): avoid output_strictly_contains failures [[#1724](https://github.com/falcosecurity/falco/pull/1724)] - [@jasondellaluce](https://github.com/jasondellaluce)
* Remove duplicate allowed ecr registry rule [[#1725](https://github.com/falcosecurity/falco/pull/1725)] - [@TomKeyte](https://github.com/TomKeyte)
* docs(RELEASE.md): switch to 3 releases per year [[#1711](https://github.com/falcosecurity/falco/pull/1711)] - [@leogr](https://github.com/leogr)
## v0.29.1
Released on 2021-06-29
### Major Changes
### Minor Changes
* update: bump the Falco engine version to version 9 [[#1675](https://github.com/falcosecurity/falco/pull/1675)] - [@leodido](https://github.com/leodido)
@@ -20,7 +59,7 @@ Released on 2021-06-29
### Non user-facing changes
* docs(release.md): update steps [[#1684](https://github.com/falcosecurity/falco/pull/1684)] - [@maxgio92](https://github.com/maxgio92
* docs(release.md): update steps [[#1684](https://github.com/falcosecurity/falco/pull/1684)] - [@maxgio92](https://github.com/maxgio92)
## v0.29.0

View File

@@ -110,12 +110,12 @@ set(CMD_MAKE make)
include(ExternalProject)
# LuaJIT
include(luajit)
# libs
include(falcosecurity-libs)
# LuaJit provided by libs
include(luajit)
# jq
include(jq)
@@ -143,6 +143,9 @@ if(NOT MINIMAL_BUILD)
# libcurl
include(curl)
# civetweb
include(civetweb)
endif()
# Lpeg
@@ -157,24 +160,6 @@ include(lyaml)
# One TBB
include(tbb)
if(NOT MINIMAL_BUILD)
# civetweb
set(CIVETWEB_SRC "${PROJECT_BINARY_DIR}/civetweb-prefix/src/civetweb/")
set(CIVETWEB_LIB "${CIVETWEB_SRC}/install/lib/libcivetweb.a")
set(CIVETWEB_INCLUDE_DIR "${CIVETWEB_SRC}/install/include")
message(STATUS "Using bundled civetweb in '${CIVETWEB_SRC}'")
ExternalProject_Add(
civetweb
URL "https://github.com/civetweb/civetweb/archive/v1.11.tar.gz"
URL_HASH "SHA256=de7d5e7a2d9551d325898c71e41d437d5f7b51e754b242af897f7be96e713a42"
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E make_directory ${CIVETWEB_SRC}/install/lib
COMMAND ${CMAKE_COMMAND} -E make_directory ${CIVETWEB_SRC}/install/include
BUILD_IN_SOURCE 1
BUILD_COMMAND ${CMD_MAKE} COPT="-DNO_FILES" WITH_CPP=1
BUILD_BYPRODUCTS ${CIVETWEB_LIB}
INSTALL_COMMAND ${CMD_MAKE} COPT="-DNO_FILES" install-lib install-headers PREFIX=${CIVETWEB_SRC}/install "WITH_CPP=1")
endif()
#string-view-lite
include(DownloadStringViewLite)
@@ -212,6 +197,7 @@ include(static-analysis)
# Shared build variables
set(FALCO_SINSP_LIBRARY sinsp)
set(FALCO_SHARE_DIR share/falco)
set(FALCO_PLUGINS_DIR ${FALCO_SHARE_DIR}/plugins)
set(FALCO_ABSOLUTE_SHARE_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}")
set(FALCO_BIN_DIR bin)
@@ -220,5 +206,7 @@ add_subdirectory(userspace/engine)
add_subdirectory(userspace/falco)
add_subdirectory(tests)
include(plugins)
# Packages configuration
include(CPackConfig)

2
OWNERS
View File

@@ -4,6 +4,7 @@ approvers:
- leodido
- mstemm
- leogr
- jasondellaluce
reviewers:
- fntlnz
- kaizhe
@@ -12,3 +13,4 @@ reviewers:
- mfdii
- mstemm
- leogr
- jasondellaluce

View File

@@ -4,7 +4,9 @@ Our release process is mostly automated, but we still need some manual steps to
Changes and new features are grouped in [milestones](https://github.com/falcosecurity/falco/milestones), the milestone with the next version represents what is going to be released.
A release happens every two months ([as per community discussion](https://github.com/falcosecurity/community/blob/master/meeting-notes/2020-09-30.md#agenda)), and we need to assign owners for each (usually we pair a new person with an experienced one). Assignees and the due date are proposed during the [weekly community call](https://github.com/falcosecurity/community). Note that hotfix releases can happen as soon as it is needed.
Falco releases are due to happen 3 times per year. Our current schedule sees a new release by the end of January, May, and September each year. Hotfix releases can happen whenever it's needed.
Moreover, we need to assign owners for each release (usually we pair a new person with an experienced one). Assignees and the due date are proposed during the [weekly community call](https://github.com/falcosecurity/community).
Finally, on the proposed due date the assignees for the upcoming release proceed with the processes described below.

View File

@@ -14,8 +14,8 @@ include(ExternalProject)
set(FAKEIT_INCLUDE ${CMAKE_BINARY_DIR}/fakeit-prefix/include)
set(FAKEIT_EXTERNAL_URL URL https://github.com/eranpeer/fakeit/archive/2.0.5.tar.gz URL_HASH
SHA256=298539c773baca6ecbc28914306bba19d1008e098f8adc3ad3bb00e993ecdf15)
set(FAKEIT_EXTERNAL_URL URL https://github.com/eranpeer/fakeit/archive/2.0.9.tar.gz URL_HASH
SHA256=dc4ee7b17a84c959019b92c20fce6dc9426e9e170b6edf84db6cb2e188520cd7)
ExternalProject_Add(
fakeit-external

View File

@@ -21,7 +21,7 @@ if(NOT FALCO_VERSION)
git_get_exact_tag(FALCO_TAG)
if(NOT FALCO_TAG)
# Obtain the closest tag
git_describe(FALCO_VERSION "--always" "--tags")
git_describe(FALCO_VERSION "--always" "--tags" "--abbrev=7")
# Fallback version
if(FALCO_VERSION MATCHES "NOTFOUND$")
set(FALCO_VERSION "0.0.0")
@@ -31,29 +31,33 @@ if(NOT FALCO_VERSION)
else()
# A tag has been found: use it as the Falco version
set(FALCO_VERSION "${FALCO_TAG}")
# Remove the starting "v" in case there is one
string(REGEX REPLACE "^v(.*)" "\\1" FALCO_VERSION "${FALCO_TAG}")
endif()
# TODO(leodido) > ensure Falco version is semver before extracting parts Populate partial version variables
string(REGEX MATCH "^(0|[1-9][0-9]*)" FALCO_VERSION_MAJOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\..*" "\\2" FALCO_VERSION_MINOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*).*" "\\3" FALCO_VERSION_PATCH
"${FALCO_VERSION}")
string(
REGEX
REPLACE
"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*).*"
"\\5"
FALCO_VERSION_PRERELEASE
"${FALCO_VERSION}")
if(FALCO_VERSION_PRERELEASE STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_PRERELEASE "")
endif()
if(NOT FALCO_VERSION_BUILD)
string(REGEX REPLACE ".*\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)" "\\1" FALCO_VERSION_BUILD "${FALCO_VERSION}")
endif()
if(FALCO_VERSION_BUILD STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_BUILD "")
endif()
endif()
# Remove the starting "v" in case there is one
string(REGEX REPLACE "^v(.*)" "\\1" FALCO_VERSION "${FALCO_VERSION}")
# TODO(leodido) > ensure Falco version is semver before extracting parts Populate partial version variables
string(REGEX MATCH "^(0|[1-9][0-9]*)" FALCO_VERSION_MAJOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\..*" "\\2" FALCO_VERSION_MINOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*).*" "\\3" FALCO_VERSION_PATCH
"${FALCO_VERSION}")
string(
REGEX
REPLACE
"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*).*"
"\\5"
FALCO_VERSION_PRERELEASE
"${FALCO_VERSION}")
if(FALCO_VERSION_PRERELEASE STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_PRERELEASE "")
endif()
if(NOT FALCO_VERSION_BUILD)
string(REGEX REPLACE ".*\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)" "\\1" FALCO_VERSION_BUILD "${FALCO_VERSION}")
endif()
if(FALCO_VERSION_BUILD STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_BUILD "")
endif()
message(STATUS "Falco version: ${FALCO_VERSION}")

View File

@@ -0,0 +1,52 @@
#
# Copyright (C) 2021 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
set(CIVETWEB_SRC "${PROJECT_BINARY_DIR}/civetweb-prefix/src/civetweb/")
set(CIVETWEB_LIB "${CIVETWEB_SRC}/install/${CMAKE_INSTALL_LIBDIR}/libcivetweb.a")
SET(CIVETWEB_CPP_LIB "${CIVETWEB_SRC}/install/${CMAKE_INSTALL_LIBDIR}/libcivetweb-cpp.a")
set(CIVETWEB_INCLUDE_DIR "${CIVETWEB_SRC}/install/include")
message(STATUS "Using bundled civetweb in '${CIVETWEB_SRC}'")
if (USE_BUNDLED_OPENSSL)
ExternalProject_Add(
civetweb
DEPENDS openssl
URL "https://github.com/civetweb/civetweb/archive/v1.15.tar.gz"
URL_HASH "SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9"
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off
-DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=off
-DCIVETWEB_SERVE_NO_FILES=on
-DCMAKE_INSTALL_PREFIX=${CIVETWEB_SRC}/install
-DOPENSSL_ROOT_DIR:PATH=${OPENSSL_INSTALL_DIR}
-DOPENSSL_USE_STATIC_LIBS:BOOL=TRUE
BUILD_BYPRODUCTS ${CIVETWEB_LIB} ${CIVETWEB_CPP_LIB})
else()
ExternalProject_Add(
civetweb
URL "https://github.com/civetweb/civetweb/archive/v1.15.tar.gz"
URL_HASH "SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9"
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off
-DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=off
-DCIVETWEB_SERVE_NO_FILES=on
-DCMAKE_INSTALL_PREFIX=${CIVETWEB_SRC}/install
BUILD_BYPRODUCTS ${CIVETWEB_LIB} ${CIVETWEB_CPP_LIB})
endif()

View File

@@ -24,5 +24,4 @@ ExternalProject_Add(
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
PATCH_COMMAND patch -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch/libscap.patch && patch -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch/luajit.patch)
TEST_COMMAND "")

View File

@@ -1,49 +0,0 @@
diff --git a/userspace/libscap/scap.c b/userspace/libscap/scap.c
index 6f51588e..5f9ea84e 100644
--- a/userspace/libscap/scap.c
+++ b/userspace/libscap/scap.c
@@ -55,7 +55,7 @@ limitations under the License.
//#define NDEBUG
#include <assert.h>
-static const char *SYSDIG_BPF_PROBE_ENV = "SYSDIG_BPF_PROBE";
+static const char *SYSDIG_BPF_PROBE_ENV = "FALCO_BPF_PROBE";
//
// Probe version string size
@@ -114,7 +114,7 @@ scap_t* scap_open_udig_int(char *error, int32_t *rc,
static uint32_t get_max_consumers()
{
uint32_t max;
- FILE *pfile = fopen("/sys/module/" PROBE_DEVICE_NAME "_probe/parameters/max_consumers", "r");
+ FILE *pfile = fopen("/sys/module/" PROBE_DEVICE_NAME "/parameters/max_consumers", "r");
if(pfile != NULL)
{
int w = fscanf(pfile, "%"PRIu32, &max);
@@ -186,7 +186,7 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
return NULL;
}
- snprintf(buf, sizeof(buf), "%s/.sysdig/%s-bpf.o", home, PROBE_NAME);
+ snprintf(buf, sizeof(buf), "%s/.falco/%s-bpf.o", home, PROBE_NAME);
bpf_probe = buf;
}
}
@@ -344,7 +344,7 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
else if(errno == EBUSY)
{
uint32_t curr_max_consumers = get_max_consumers();
- snprintf(error, SCAP_LASTERR_SIZE, "Too many sysdig instances attached to device %s. Current value for /sys/module/" PROBE_DEVICE_NAME "_probe/parameters/max_consumers is '%"PRIu32"'.", filename, curr_max_consumers);
+ snprintf(error, SCAP_LASTERR_SIZE, "Too many Falco instances attached to device %s. Current value for /sys/module/" PROBE_DEVICE_NAME "/parameters/max_consumers is '%"PRIu32"'.", filename, curr_max_consumers);
}
else
{
@@ -2175,7 +2175,7 @@ int32_t scap_disable_dynamic_snaplen(scap_t* handle)
const char* scap_get_host_root()
{
- char* p = getenv("SYSDIG_HOST_ROOT");
+ char* p = getenv("HOST_ROOT");
static char env_str[SCAP_MAX_PATH_SIZE + 1];
static bool inited = false;
if (! inited) {

View File

@@ -1,57 +0,0 @@
diff --git a/userspace/chisel/chisel.cpp b/userspace/chisel/chisel.cpp
index 0a6e3cf8..0c2e255a 100644
--- a/userspace/chisel/chisel.cpp
+++ b/userspace/chisel/chisel.cpp
@@ -98,7 +98,7 @@ void lua_stackdump(lua_State *L)
// Lua callbacks
///////////////////////////////////////////////////////////////////////////////
#ifdef HAS_LUA_CHISELS
-const static struct luaL_reg ll_sysdig [] =
+const static struct luaL_Reg ll_sysdig [] =
{
{"set_filter", &lua_cbacks::set_global_filter},
{"set_snaplen", &lua_cbacks::set_snaplen},
@@ -134,7 +134,7 @@ const static struct luaL_reg ll_sysdig [] =
{NULL,NULL}
};
-const static struct luaL_reg ll_chisel [] =
+const static struct luaL_Reg ll_chisel [] =
{
{"request_field", &lua_cbacks::request_field},
{"set_filter", &lua_cbacks::set_filter},
@@ -146,7 +146,7 @@ const static struct luaL_reg ll_chisel [] =
{NULL,NULL}
};
-const static struct luaL_reg ll_evt [] =
+const static struct luaL_Reg ll_evt [] =
{
{"field", &lua_cbacks::field},
{"get_num", &lua_cbacks::get_num},
diff --git a/userspace/chisel/lua_parser.cpp b/userspace/chisel/lua_parser.cpp
index 0e26617d..78810d96 100644
--- a/userspace/chisel/lua_parser.cpp
+++ b/userspace/chisel/lua_parser.cpp
@@ -32,7 +32,7 @@ extern "C" {
#include "lauxlib.h"
}
-const static struct luaL_reg ll_filter [] =
+const static struct luaL_Reg ll_filter [] =
{
{"rel_expr", &lua_parser_cbacks::rel_expr},
{"bool_op", &lua_parser_cbacks::bool_op},
diff --git a/userspace/chisel/lua_parser_api.cpp b/userspace/chisel/lua_parser_api.cpp
index c89e9126..c3d8008a 100644
--- a/userspace/chisel/lua_parser_api.cpp
+++ b/userspace/chisel/lua_parser_api.cpp
@@ -266,7 +266,7 @@ int lua_parser_cbacks::rel_expr(lua_State *ls)
string err = "Got non-table as in-expression operand\n";
throw sinsp_exception("parser API error");
}
- int n = luaL_getn(ls, 4); /* get size of table */
+ int n = lua_objlen (ls, 4); /* get size of table */
for (i=1; i<=n; i++)
{
lua_rawgeti(ls, 4, i);

View File

@@ -16,26 +16,31 @@ set(FALCOSECURITY_LIBS_CMAKE_WORKING_DIR "${CMAKE_BINARY_DIR}/falcosecurity-libs
file(MAKE_DIRECTORY ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR})
# The falcosecurity/libs git reference (branch name, commit hash, or tag) To update falcosecurity/libs version for the next release, change the
# default below In case you want to test against another falcosecurity/libs version just pass the variable - ie., `cmake
# -DFALCOSECURITY_LIBS_VERSION=dev ..`
if(NOT FALCOSECURITY_LIBS_VERSION)
set(FALCOSECURITY_LIBS_VERSION "17f5df52a7d9ed6bb12d3b1768460def8439936d")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=34a2a466f1e5045591f102de2bc812d9b4f0d5874094cc73b97a7970fb2a3a18")
if(FALCOSECURITY_LIBS_SOURCE_DIR)
set(FALCOSECURITY_LIBS_VERSION "local")
message(STATUS "Using local falcosecurity/libs in '${FALCOSECURITY_LIBS_SOURCE_DIR}'")
else()
# The falcosecurity/libs git reference (branch name, commit hash, or tag) To update falcosecurity/libs version for the next release, change the
# default below In case you want to test against another falcosecurity/libs version just pass the variable - ie., `cmake
# -DFALCOSECURITY_LIBS_VERSION=dev ..`
if(NOT FALCOSECURITY_LIBS_VERSION)
set(FALCOSECURITY_LIBS_VERSION "bb9bee8e522fc953c2a79093d688d3d82b925e8b")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=ab2f18ff9c8d92dd06088ccfa73d4230fce3617613229f5afd839a37c13b0459")
endif()
# cd /path/to/build && cmake /path/to/source
execute_process(COMMAND "${CMAKE_COMMAND}" -DFALCOSECURITY_LIBS_VERSION=${FALCOSECURITY_LIBS_VERSION} -DFALCOSECURITY_LIBS_CHECKSUM=${FALCOSECURITY_LIBS_CHECKSUM}
${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR})
# todo(leodido, fntlnz) > use the following one when CMake version will be >= 3.13
# execute_process(COMMAND "${CMAKE_COMMAND}" -B ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR} WORKING_DIRECTORY
# "${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR}")
execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}")
set(FALCOSECURITY_LIBS_SOURCE_DIR "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}/falcosecurity-libs-prefix/src/falcosecurity-libs")
endif()
# cd /path/to/build && cmake /path/to/source
execute_process(COMMAND "${CMAKE_COMMAND}" -DFALCOSECURITY_LIBS_VERSION=${FALCOSECURITY_LIBS_VERSION} -DFALCOSECURITY_LIBS_CHECKSUM=${FALCOSECURITY_LIBS_CHECKSUM}
${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR})
# todo(leodido, fntlnz) > use the following one when CMake version will be >= 3.13
# execute_process(COMMAND "${CMAKE_COMMAND}" -B ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR} WORKING_DIRECTORY
# "${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR}")
execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}")
set(FALCOSECURITY_LIBS_SOURCE_DIR "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}/falcosecurity-libs-prefix/src/falcosecurity-libs")
add_definitions(-D_GNU_SOURCE)
add_definitions(-DHAS_CAPTURE)
if(MUSL_OPTIMIZED_BUILD)
@@ -43,6 +48,10 @@ if(MUSL_OPTIMIZED_BUILD)
endif()
set(PROBE_VERSION "${FALCOSECURITY_LIBS_VERSION}")
set(PROBE_NAME "falco")
set(DRIVER_PACKAGE_NAME "falco")
set(SCAP_BPF_PROBE_ENV_VAR_NAME "FALCO_BPF_PROBE")
set(SCAP_HOST_ROOT_ENV_VAR_NAME "HOST_ROOT")
if(NOT LIBSCAP_DIR)
set(LIBSCAP_DIR "${FALCOSECURITY_LIBS_SOURCE_DIR}")
@@ -60,8 +69,19 @@ set(WITH_CHISEL ON CACHE BOOL "")
set(USE_BUNDLED_TBB ON CACHE BOOL "")
set(USE_BUNDLED_B64 ON CACHE BOOL "")
set(USE_BUNDLED_JSONCPP ON CACHE BOOL "")
set(USE_BUNDLED_LUAJIT ON CACHE BOOL "")
list(APPEND CMAKE_MODULE_PATH "${FALCOSECURITY_LIBS_SOURCE_DIR}/cmake/modules")
include(CheckSymbolExists)
check_symbol_exists(strlcpy "string.h" HAVE_STRLCPY)
if(HAVE_STRLCPY)
message(STATUS "Existing strlcpy found, will *not* use local definition by setting -DHAVE_STRLCPY.")
add_definitions(-DHAVE_STRLCPY)
else()
message(STATUS "No strlcpy found, will use local definition")
endif()
include(libscap)
include(libsinsp)

View File

@@ -1,30 +0,0 @@
#
# Copyright (C) 2020 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
if(NOT LUAJIT_INCLUDE)
set(LUAJIT_SRC "${PROJECT_BINARY_DIR}/luajit-prefix/src/luajit/src")
message(STATUS "Using bundled LuaJIT in '${LUAJIT_SRC}'")
set(LUAJIT_INCLUDE "${LUAJIT_SRC}")
set(LUAJIT_LIB "${LUAJIT_SRC}/libluajit.a")
externalproject_add(
luajit
GIT_REPOSITORY "https://github.com/LuaJIT/LuaJIT"
GIT_TAG "1d8b747c161db457e032a023ebbff511f5de5ec2"
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
BUILD_BYPRODUCTS ${LUAJIT_LIB}
INSTALL_COMMAND ""
)
endif()
include_directories("${LUAJIT_INCLUDE}")

View File

@@ -11,9 +11,10 @@
# specific language governing permissions and limitations under the License.
#
set(LYAML_SRC "${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/ext/yaml")
set(LYAML_LIB "${LYAML_SRC}/.libs/yaml.a")
message(STATUS "Using bundled lyaml in '${LYAML_SRC}'")
set(LYAML_ROOT "${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml")
set(LYAML_LIB "${LYAML_ROOT}/ext/yaml/.libs/yaml.a")
set(LYAML_LUA_DIR "${LYAML_ROOT}/lib")
message(STATUS "Using bundled lyaml in '${LYAML_ROOT}'")
externalproject_add(
lyaml
DEPENDS luajit libyaml
@@ -22,7 +23,6 @@ externalproject_add(
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
BUILD_BYPRODUCTS ${LYAML_LIB}
INSTALL_COMMAND ""
CONFIGURE_COMMAND ./configure --enable-static CFLAGS=-I${LIBYAML_INSTALL_DIR}/include CPPFLAGS=-I${LIBYAML_INSTALL_DIR}/include LDFLAGS=-L${LIBYAML_INSTALL_DIR}/lib LIBS=-lyaml LUA=${LUAJIT_SRC}/luajit LUA_INCLUDE=-I${LUAJIT_INCLUDE}
INSTALL_COMMAND sh -c
"cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/engine/lua"
)

View File

@@ -0,0 +1,36 @@
#
# Copyright (C) 2021 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
include(ExternalProject)
set(PLUGINS_VERSION "0.1.0-rc1-28-g019437e")
ExternalProject_Add(
cloudtrail-plugin
URL "https://download.falco.org/plugins/dev/cloudtrail-${PLUGINS_VERSION}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=ad9692957c5435238e07d1625e1b247eabe98b85f54de9218367fdd73a6f3f0b"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
install(FILES "${PROJECT_BINARY_DIR}/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so" DESTINATION "${FALCO_PLUGINS_DIR}")
ExternalProject_Add(
json-plugin
URL "https://download.falco.org/plugins/dev/json-${PLUGINS_VERSION}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=721ea5226b0f623915d0d5c34870589ad33a8ff795b0daa1af72f21a67430077"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
install(FILES "${PROJECT_BINARY_DIR}/json-plugin-prefix/src/json-plugin/libjson.so" DESTINATION "${FALCO_PLUGINS_DIR}")

View File

@@ -20,7 +20,7 @@ ENV FALCO_VERSION=${FALCO_VERSION}
# build toolchain
RUN yum -y install centos-release-scl && \
INSTALL_PKGS="devtoolset-7-gcc devtoolset-7-gcc-c++ devtoolset-7-toolchain devtoolset-7-libstdc++-devel devtoolset-7-elfutils-libelf-devel llvm-toolset-7 glibc-static autoconf automake libtool createrepo expect git which libcurl-devel zlib-devel ncurses-devel rpm-build libyaml-devel" && \
INSTALL_PKGS="devtoolset-7-gcc devtoolset-7-gcc-c++ devtoolset-7-toolchain devtoolset-7-libstdc++-devel devtoolset-7-elfutils-libelf-devel llvm-toolset-7 glibc-static autoconf automake libtool createrepo expect git which libcurl-devel zlib-devel rpm-build libyaml-devel" && \
yum -y install --setopt=tsflags=nodocs $INSTALL_PKGS && \
rpm -V $INSTALL_PKGS

View File

@@ -18,7 +18,7 @@ How to use.
* docker run -ti falcosecurity/falco-builder bash
To build Falco it needs:
- a bind-mount on the source directory (ie., the directory containing Falco and sysdig source as siblings)
- a bind-mount on the source directory (ie., the directory containing the Falco source as sibling)
Optionally, you can also bind-mount the build directory.
So, you can execute it from the Falco root directory as follows.

View File

@@ -1,4 +1,4 @@
FROM debian:stable
FROM debian:buster
LABEL maintainer="cncf-falco-dev@lists.cncf.io"
@@ -30,6 +30,7 @@ RUN apt-get update \
libc6-dev \
libelf-dev \
libmpx2 \
libssl-dev \
llvm-7 \
netcat \
xz-utils \

View File

@@ -33,6 +33,32 @@ rules_file:
- /etc/falco/k8s_audit_rules.yaml
- /etc/falco/rules.d
#
# Plugins that are available for use. These plugins are not loaded by
# default, as they require explicit configuration to point to
# cloudtrail log files.
#
# To learn more about the supported formats for
# init_config/open_params for the cloudtrail plugin, see the README at
# https://github.com/falcosecurity/plugins/blob/master/plugins/cloudtrail/README.md.
plugins:
- name: cloudtrail
library_path: libcloudtrail.so
init_config: ""
open_params: ""
- name: json
library_path: libjson.so
init_config: ""
# Setting this list to empty ensures that the above plugins are *not*
# loaded and enabled by default. If you want to use the above plugins,
# set a meaningful init_config/open_params for the cloudtrail plugin
# and then change this to:
# load_plugins: [cloudtrail, json]
load_plugins: []
# If true, the times displayed in log messages and output messages
# will be in ISO 8601. By default, times are displayed in the local
# time zone, as governed by /etc/localtime.
@@ -46,6 +72,12 @@ json_output: false
# (user=root ....") in the json output.
json_include_output_property: true
# When using json output, whether or not to include the "tags" property
# itself in the json output. If set to true, outputs caused by rules
# with no tags will have a "tags" field set to an empty array. If set to
# false, the "tags" field will not be included in the json output at all.
json_include_tags_property: true
# 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: true
@@ -249,3 +281,9 @@ grpc:
# Make sure to have a consumer for them or leave this disabled.
grpc_output:
enabled: false
# Container orchestrator metadata fetching params
metadata_download:
max_mb: 100
chunk_wait_us: 1000
watch_freq_sec: 1

View File

@@ -2,6 +2,8 @@
This document reflects when and how we clean up the Falco artifacts from their storage location.
**Superseeded by**: [drivers-storage-s3 proposal](https://github.com/falcosecurity/falco/blob/master/proposals/20201025-drivers-storage-s3.md).
## Motivation
The [bintray](https://bintray.com/falcosecurity) open-source plan offers 10GB free space for storing artifacts.
@@ -94,9 +96,19 @@ Since the process of building drivers is time and resource consuming, this docum
The candidate is an AWS S3 bucket responsible for holding the deleted driver version files.
#### Notice
The current mechanism the Falco community uses to store the Falco drivers is explained by the [drivers-storage-s3](https://github.com/falcosecurity/falco/blob/master/proposals/20201025-drivers-storage-s3.md) proposal.
### Implementation
The [test-infra](https://github.com/falcosecurity/test-infra) CI, specifically its part dedicated to run the **Drivers Build Grid** that runs every time it detects changes into the `driverkit` directory of the [test-infra](https://github.com/falcosecurity/test-infra) repository,
will have a new job - called `drivers/cleanup` - responsible for removing all the Falco driver versions except the last two.
This job will be triggered after the `drivers/publish` completed successfully on the master branch.
This job will be triggered after the `drivers/publish` completed successfully on the master branch.
#### Notice
At the moment of writing (2021 09 28) the `drivers/cleanup` job is no more in place.
Pragmatically, this means that the older Falco drivers will remain available in their [S3 bucket](https://download.falco.org/?prefix=driver/).

View File

@@ -0,0 +1,613 @@
# Plugin System
## Summary
This is a proposal to create an infrastructure to extend the functionality of the Falco libraries via plugins.
Plugins will allow users to easily extend the functionality of the libraries and, as a consequence, of Falco and any other tool based on the libraries.
This proposal, in particular, focuses on two types of plugins: source plugins and extractor plugins.
## Motivation
[libscap](https://github.com/falcosecurity/libs/tree/master/userspace/libscap) and [libsinsp](https://github.com/falcosecurity/libs/tree/master/userspace/libsinsp) provide a powerful data capture framework, with a rich set of features that includes:
- data capture
- trace files management
- enrichment
- filtering
- formatting and screen rendering
- Lua scripting (chisels)
These features have been designed with one specific input in mind: system calls. However, they are generically adaptable to a broad set of inputs, such as cloud logs.
With this proposal, we want to dramatically extend the scope of what the libraries, Falco and other tools can be applied to. We want to do it in a way that is easy, efficient and empowers anyone in the community to write a plugin.
## Goals
- To design and implement a plugin framework that makes the libraries more modular and extensible
- To have a framework that is easy to use
- To support dynamic loading of plugins, so that the libraries can be extended without having to be recompiled and relinked
- To enable users to write plugins in any language, with a particular focus on Go, C and C++
- To have an efficient plugin framework so that, performance-wise, writing a plugin is as close as possible as extending the libraries internal source code
- To make it possible to write plugins for Linux, MacOS and Windows
## Non-Goals
- To implement plugins other than source and extractor: to be approached as separate task
- To document the plugin framework and interface: to be approached as separate task
## Proposal
### Plugin Common Information
Both source and extractor plugins have the following:
- A required api version, to ensure compatibility with the plugin framework.
- A name
- A description
- A version
- A contact field for the plugin authors (website, github repo, twitter, etc).
- Functions to initialize and destroy the plugin internal state.
### Plugin types
Initially, we will implement support for two types of plugins: source plugins and extractor plugins.
#### Source Plugin
A source plugin implements a new sinsp/scap event source. It has the ability to "open" and "close" a session that provides events. It also has the ability to return an event to the plugin framework via a next() method. Events returned by source plugins have an "event source", which describes the information in the event. This is distinct from the plugin name to allow for multiple kinds of plugins to generate the same kind of events. For example, there might be plugins gke-audit-bridge, eks-audit-bridge, ibmcloud-audit-bridge, etc. that all fetch [K8s Audit](https://kubernetes.io/docs/tasks/debug-application-cluster/audit/) information. The plugins would have different names but would have the same event source "k8s_audit".
Source plugins also have the ability to extract information from events based on fields. For example, a field proc.name extracts a process name from a syscall event. The plugin returns a set of supported fields, and there are functions to extract a value given an event and field. The plugin framework can then build filtering expressions/Falco rule conditions based on these fields combined with relational and/or logical operators. For example, given an expression "ct.name=root and ct.region=us-east-1", the plugin framework handles parsing the expression, calling the plugin to extract values for a given event, and determining the result of the expression. In a Falco output string like "An EC2 Node was created (name=%ec2.name region=%ct.region)", the plugin framework handles parsing the output string, calling the plugin to extract values for a given event, and building the resolved string.
Source plugins also provide an "id", which is globally unique and is used in capture files (see below).
#### Extractor Plugin
An extractor plugin focuses only on field extraction from events generated by other plugins, or by the core libraries. It does *not* provide an event source, but can extract fields from other event sources. An example is json field extraction, where a plugin might be able to extract fields from arbitrary json payloads.
An extractor plugin provides an optional set of event sources. When the framework receives an event with an event source in the plugin's set of event sources, fields in expressions/Falco outputs will be extracted from events using the plugin. An extractor plugin can also *not* name a set of event sources. In this case, fields will be extracted from *all* events, regardless of source. In this case, the exctractor plugin must detect the format of arbitrary payloads and be able to return NULL/no value when the payload is not supported.
### Support for Plugin Events in Capture Files.
libscap will define a new event type called "pluginevent" that contains two fields:
* "plugin ID": This uniquely identifies the plugin that generated this event.
* "event_data": This is a variable-length data buffer containing the event data, as returned by the plugin.
Defining an event for plugins allows creating capture files from plugins. These capture files can be saved, read, filtered, etc, like any other capture file, allowing for later analysis/display/etc.
### Plugins format
Plugins are dynamic libraries (.so files in Unix, .dll files in windows) that export a minimum set of functions that the libraries will recognize.
Plugins are versioned using semantic versioning to minimize regressions and compatibility issues.
Plugins can be written in any language, as long as they export the required functions. Go, however, is the preferred language to write plugins, followed by C/C++.
### Protecting from plugin issues
The libraries will do everything possible to validate the data coming from the plugins and protect Falco and the other consumers from corrupted data. However, for performance reasons, plugins will be "trusted": they will run in the same thread and address space as Falco and they could crash the program. We assume that the user will be in control of plugin loading and will make sure only trusted plugins are loaded/packaged with Falco.
### Plugin/Event Source registries
Every source plugin requires its own, unique plugin ID to interoperate with Falco and the other plugins. The plugin ID will be used by the libs to properly process incoming events (for example, when saving events to file and loading them back), and by plugins to unuambiguosly recognize their dependencies.
To facilitate the allocation and distribution of plugin IDs, we will require that plugin developers request IDs for their plugins to the Falco organization. The mechanism used for plugin allocation is not determined yet and will be discussed in the future.
Similarly, plugin developers must register event sources with the Falco organization. This allows coordination between plugins that wish to provide compatible payloads, and to allow extractor plugins to know what data format is associated with a given event source.
### golang plugin SDK
To facilitate the development of plugins written in go, an SDK has been developed. We intend this SDK (and future SDKs for other languages) to be part of the Falco organization. For this reason, we submitted the following incubation request: https://github.com/falcosecurity/evolution/issues/62
### Proposed API (subject to change)
```c
// This struct represents an event returned by the plugin, and is used
// below in next()/next_batch().
// - data: pointer to a memory buffer pointer. The plugin will set it
// to point to the memory containing the next event. Once returned,
// the memory is owned by the plugin framework and will be freed via
// a call to free().
// - datalen: pointer to a 32bit integer. The plugin will set it the size of the
// buffer pointed by data.
// - ts: the event timestamp. Can be (uint64_t)-1, in which case the engine will
// automatically fill the event time with the current time.
typedef struct ss_plugin_event
{
uint8_t *data;
uint32_t datalen;
uint64_t ts;
} ss_plugin_event;
//
// This is the opaque pointer to the state of a plugin.
// It points to any data that might be needed plugin-wise. It is
// allocated by init() and must be destroyed by destroy().
// It is defined as void because the engine doesn't care what it is
// and it treats is as opaque.
//
typedef void ss_plugin_t;
//
// This is the opaque pointer to the state of an open instance of the source
// plugin.
// It points to any data that is needed while a capture is running. It is
// allocated by open() and must be destroyed by close().
// It is defined as void because the engine doesn't care what it is
// and it treats is as opaque.
//
typedef void ss_instance_t;
//
// Interface for a sinsp/scap source plugin
//
//
// NOTE: For all functions below that return a char *, the memory
// pointed to by the char * must be allocated by the plugin using
// malloc() and should be freed by the caller using free().
//
// For each function below, the exported symbol from the dynamic
// library should have a prefix of "plugin_"
// (e.g. plugin_get_required_api_version, plugin_init, etc.)
//
typedef struct
{
//
// Return the version of the plugin API used by this plugin.
// Required: yes
// Return value: the API version string, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// NOTE: to ensure correct interoperability between the engine and the plugins,
// we use a semver approach. Plugins are required to specify the version
// of the API they run against, and the engine will take care of checking
// and enforcing compatibility.
//
char* (*get_required_api_version)();
//
// Return the plugin type.
// Required: yes
// Should return TYPE_SOURCE_PLUGIN. It still makes sense to
// have a function get_type() as the plugin interface will
// often dlsym() functions from shared libraries, and can't
// inspect any C struct type.
//
uint32_t (*get_type)();
//
// Initialize the plugin and, if needed, allocate its state.
// Required: yes
// Arguments:
// - config: a string with the plugin configuration. The format of the
// string is chosen by the plugin itself.
// - rc: pointer to an integer that will contain the initialization result,
// as a SCAP_* value (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1)
// Return value: pointer to the plugin state that will be treated as opaque
// by the engine and passed to the other plugin functions.
// If rc is SCAP_FAILURE, this function should return NULL.
//
ss_plugin_t* (*init)(char* config, int32_t* rc);
//
// Destroy the plugin and, if plugin state was allocated, free it.
// Required: yes
//
void (*destroy)(ss_plugin_t* s);
//
// Return a string with the error that was last generated by
// the plugin.
// Required: yes
//
// In cases where any other api function returns an error, the
// plugin should be prepared to return a human-readable error
// string with more context for the error. The plugin manager
// calls get_last_error() to access that string.
//
char* (*get_last_error)(ss_plugin_t* s);
//
// Return the unique ID of the plugin.
// Required: yes
// EVERY SOURCE PLUGIN (see get_type()) MUST OBTAIN AN OFFICIAL ID FROM THE
// FALCOSECURITY ORGANIZATION, OTHERWISE IT WON'T PROPERLY COEXIST WITH OTHER PLUGINS.
//
uint32_t (*get_id)();
//
// Return the name of the plugin, which will be printed when displaying
// information about the plugin.
// Required: yes
//
char* (*get_name)();
//
// Return the descriptions of the plugin, which will be printed when displaying
// information about the plugin or its events.
// Required: yes
//
char* (*get_description)();
//
// Return a string containing contact info (url, email, twitter, etc) for
// the plugin authors.
// Required: yes
//
char* (*get_contact)();
//
// Return the version of this plugin itself
// Required: yes
// Return value: a string with a version identifier, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// This differs from the api version in that this versions the
// plugin itself, as compared to the plugin interface. When
// reading capture files, the major version of the plugin that
// generated events must match the major version of the plugin
// used to read events.
//
char* (*get_version)();
//
// Return a string describing the events generated by this source plugin.
// Required: yes
// Example event sources would be strings like "syscall",
// "k8s_audit", etc. The source can be used by extractor
// plugins to filter the events they receive.
//
char* (*get_event_source)();
//
// Return the list of extractor fields exported by this plugin. Extractor
// fields can be used in Falco rule conditions and sysdig filters.
// Required: no
// Return value: a string with the list of fields encoded as a json
// array.
// Each field entry is a json object with the following properties:
// "type": one of "string", "uint64"
// "name": a string with a name for the field
// "desc": a string with a description of the field
// Example return value:
// [
// {"type": "string", "name": "field1", "desc": "Describing field 1"},
// {"type": "uint64", "name": "field2", "desc": "Describing field 2"}
// ]
char* (*get_fields)();
//
// Open the source and start a capture.
// Required: yes
// Arguments:
// - s: the plugin state returned by init()
// - params: the open parameters, as a string. The format is defined by the plugin
// itsef
// - rc: pointer to an integer that will contain the open result, as a SCAP_* value
// (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1)
// Return value: a pointer to the open context that will be passed to next(),
// close(), event_to_string() and extract_*.
//
ss_instance_t* (*open)(ss_plugin_t* s, char* params, int32_t* rc);
//
// Close a capture.
// Required: yes
// Arguments:
// - s: the plugin context, returned by init(). Can be NULL.
// - h: the capture context, returned by open(). Can be NULL.
//
void (*close)(ss_plugin_t* s, ss_instance_t* h);
//
// Return the next event.
// Required: yes
// Arguments:
// - s: the plugin context, returned by init(). Can be NULL.
// - h: the capture context, returned by open(). Can be NULL.
//
// - evt: pointer to a ss_plugin_event pointer. The plugin should
// allocate a ss_plugin_event struct using malloc(), as well as
// allocate the data buffer within the ss_plugin_event struct.
// Both the struct and data buffer are owned by the plugin framework
// and will free them using free().
//
// Return value: the status of the operation (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1,
// SCAP_TIMEOUT=-1)
//
int32_t (*next)(ss_plugin_t* s, ss_instance_t* h, ss_plugin_event **evt);
//
// Return the read progress.
// Required: no
// Arguments:
// - progress_pct: the read progress, as a number between 0 (no data has been read)
// and 10000 (100% of the data has been read). This encoding allows the engine to
// print progress decimals without requiring to deal with floating point numbers
// (which could cause incompatibility problems with some languages).
// Return value: a string representation of the read
// progress. This might include the progress percentage
// combined with additional context added by the plugin. If
// NULL, progress_pct should be used.
// NOTE: reporting progress is optional and in some case could be impossible. However,
// when possible, it's recommended as it provides valuable information to the
// user.
//
char* (*get_progress)(ss_plugin_t* s, ss_instance_t* h, uint32_t* progress_pct);
//
// Return a text representation of an event generated by this source plugin.
// Required: yes
// Arguments:
// - data: the buffer from an event produced by next().
// - datalen: the length of the buffer from an event produced by next().
// Return value: the text representation of the event. This is used, for example,
// by sysdig to print a line for the given event.
//
char *(*event_to_string)(ss_plugin_t *s, const uint8_t *data, uint32_t datalen);
//
// Extract a filter field value from an event.
// We offer multiple versions of extract(), differing from each other only in
// the type of the value they return (string, integer...).
// Required: no
// Arguments:
// - evtnum: the number of the event that is bein processed
// - id: the numeric identifier of the field to extract. It corresponds to the
// position of the field in the array returned by get_fields().
// - arg: the field argument, if an argument has been specified for the field,
// otherwise it's NULL. For example:
// * if the field specified by the user is foo.bar[pippo], arg will be the
// string "pippo"
// * if the field specified by the user is foo.bar, arg will be NULL
// - data: the buffer produced by next().
// - datalen: the length of the buffer produced by next().
// - field_present: nonzero if the field is present for the given event.
// Return value: the produced value of the filter field. For extract_str(), a
// NULL return value means that the field is missing for the given event.
//
char *(*extract_str)(ss_plugin_t *s, uint64_t evtnum, const char * field, const char *arg, uint8_t *data, uint32_t datalen);
uint64_t (*extract_u64)(ss_plugin_t *s, uint64_t evtnum, const char *field, const char *arg, uint8_t *data, uint32_t datalen, uint32_t *field_present);
//
// This is an optional, internal, function used to speed up event capture by
// batching the calls to next().
// On success:
// - nevts will be filled in with the number of events.
// - evts: pointer to an ss_plugin_event pointer. The plugin should
// allocate an array of contiguous ss_plugin_event structs using malloc(),
// as well as allocate each data buffer within each ss_plugin_event
// struct using malloc(). Both the array of structs and each data buffer are
// owned by the plugin framework and will free them using free().
// Required: no
//
int32_t (*next_batch)(ss_plugin_t* s, ss_instance_t* h, uint32_t *nevts, ss_plugin_event **evts);
//
// This is an optional, internal, function used to speed up value extraction
// Required: no
//
int32_t (*register_async_extractor)(ss_plugin_t *s, async_extractor_info *info);
//
// The following members are PRIVATE for the engine and should not be touched.
//
ss_plugin_t* state;
ss_instance_t* handle;
uint32_t id;
char *name;
} source_plugin_info;
//
// Interface for a sinsp/scap extractor plugin
//
//
// NOTE: For all functions below that return a char *, the memory
// pointed to by the char * must be allocated by the plugin using
// malloc() and should be freed by the caller using free().
//
typedef struct
{
//
// Return the version of the plugin API used by this plugin.
// Required: yes
// Return value: the API version string, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// NOTE: to ensure correct interoperability between the engine and the plugins,
// we use a semver approach. Plugins are required to specify the version
// of the API they run against, and the engine will take care of checking
// and enforcing compatibility.
//
char* (*get_required_api_version)();
//
// Return the plugin type.
// Required: yes
// Should return TYPE_EXTRACTOR_PLUGIN. It still makes sense to
// have a function get_type() as the plugin interface will
// often dlsym() functions from shared libraries, and can't
// inspect any C struct type.
//
uint32_t (*get_type)();
//
// Initialize the plugin and, if needed, allocate its state.
// Required: yes
// Arguments:
// - config: a string with the plugin configuration. The format of the
// string is chosen by the plugin itself.
// - rc: pointer to an integer that will contain the initialization result,
// as a SCAP_* value (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1)
// Return value: pointer to the plugin state that will be treated as opaque
// by the engine and passed to the other plugin functions.
//
ss_plugin_t* (*init)(char* config, int32_t* rc);
//
// Destroy the plugin and, if plugin state was allocated, free it.
// Required: yes
//
void (*destroy)(ss_plugin_t* s);
//
// Return a string with the error that was last generated by
// the plugin.
// Required: yes
//
// In cases where any other api function returns an error, the
// plugin should be prepared to return a human-readable error
// string with more context for the error. The plugin manager
// calls get_last_error() to access that string.
//
char* (*get_last_error)(ss_plugin_t* s);
//
// Return the name of the plugin, which will be printed when displaying
// information about the plugin.
// Required: yes
//
char* (*get_name)();
//
// Return the descriptions of the plugin, which will be printed when displaying
// information about the plugin or its events.
// Required: yes
//
char* (*get_description)();
//
// Return a string containing contact info (url, email, twitter, etc) for
// the plugin author.
// Required: yes
//
char* (*get_contact)();
//
// Return the version of this plugin itself
// Required: yes
// Return value: a string with a version identifier, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// This differs from the api version in that this versions the
// plugin itself, as compared to the plugin interface. When
// reading capture files, the major version of the plugin that
// generated events must match the major version of the plugin
// used to read events.
//
char* (*get_version)();
//
// Return a string describing the event sources that this
// extractor plugin can consume.
// Required: no
// Return value: a json array of strings containing event
// sources returned by a source plugin's get_event_source()
// function.
// This function is optional--if NULL then the exctractor
// plugin will receive every event.
//
char* (*get_extract_event_sources)();
//
// Return the list of extractor fields exported by this plugin. Extractor
// fields can be used in Falco rules and sysdig filters.
// Required: yes
// Return value: a string with the list of fields encoded as a json
// array.
//
char* (*get_fields)();
//
// Extract a filter field value from an event.
// We offer multiple versions of extract(), differing from each other only in
// the type of the value they return (string, integer...).
// Required: for plugins of type TYPE_EXTRACTOR_PLUGIN only
// Arguments:
// - evtnum: the number of the event that is being processed
// - id: the numeric identifier of the field to extract. It corresponds to the
// position of the field in the array returned by get_fields().
// - arg: the field argument, if an argument has been specified for the field,
// otherwise it's NULL. For example:
// * if the field specified by the user is foo.bar[pippo], arg will be the
// string "pippo"
// * if the field specified by the user is foo.bar, arg will be NULL
// - data: the buffer produced by next().
// - datalen: the length of the buffer produced by next().
// - field_present: nonzero if the field is present for the given event.
// Return value: the produced value of the filter field. For extract_str(), a
// NULL return value means that the field is missing for the given event.
//
char *(*extract_str)(ss_plugin_t *s, uint64_t evtnum, const char *field, const char *arg, uint8_t *data, uint32_t datalen);
uint64_t (*extract_u64)(ss_plugin_t *s, uint64_t evtnum, const char *field, const char *arg, uint8_t *data, uint32_t datalen, uint32_t *field_present);
} extractor_plugin_info;
```
### Event Sources and Falco Rules
Falco rules already have the notion of a "source", using the source property in rules objects, and there are currently two kinds of event sources: "syscall" and "k8s_audit". We will use the source property in Falco rules to map a given rule to the event source on which the rule runs.
For example, given a plugin with source "aws_cloudtrail", and a Falco rule with source "aws_cloudtrail", the rule will be evaluated for any events generated by the plugin.
Similarly, an extractor plugin that includes "aws_cloudtrail" in its set of event sources will have the opportunity to extract information from aws_cloudtrail events if a matching field is found in the rule's condition, exception, or output properties.
This, combined with the restrictions below, allows a set of loaded rules files to contain a mix of rules for plugins as well as "core" syscall/k8s_audit events.
We will also make a change to compile rules/macros/lists selectively based on the set of loaded plugins (specifically, their event sources), instead of unconditionally as Falco is started. This is especially important for macros, which do not contain a source property, but might contain fields that are only implemented by a given plugin.
### Handling Duplicate/Overlapping Fields in Plugins/Libraries Core
At an initial glance, adding plugins introduces the possibility of tens/hundreds of new filtercheck fields that could potentially overlap/conflict. For example, what happens if a plugin defines a "proc.name" field? However, the notion of "event source" makes these potential conflicts managable.
Remember that field extraction is always done in the context of an event, and each event can be mapped back to an event source. So we only need to ensure that filtercheck fields are distinct for a given event source. For example, it's perfectly valid for an AWS Cloudtrail plugin to define a proc.name field, as the events generated by that plugin are wholly separate from syscall events. For syscall events, the AWS Cloudtrail plugin is not involved and the core libraries extract the process name for the tid performing a syscall. For AWS Cloudtrail events, the core libraries are not involved in field extraction and is performed by the AWS Cloudtrail plugin instead.
We only need to ensure the following:
* That only one plugin is loaded at a time that exports a given event source. For example, the libraries can load either a gke-audit-bridge plugin with event source k8s_audit, or eks-audit-bridge with event source k8s_audit, but not both.
* That for a mix of source and extractor plugins having the same event source, that the fields are distinct. For example, a source plugin with source k8s_audit can export ka.* fields, and an extractor plugin with event source k8s_audit can export a jevt.value[/...] field, and the appropriate plugin will be used to extract fields from k8s_audit events as fields are parsed from condition expressions/output format strings.
### Plugin Versions and Falco Rules
To allow rules files to document the plugin versions they are compatible with, we will add a new top-level field `required_plugin_versions` to the Falco rules file format. The field is optional, and if not provided no plugin compatibility checks will be performed. The syntax of `required_plugin_versions` will be the following:
```yaml
- required_plugin_versions:
- name: <plugin_name>
version: x.y.z
...
```
Below required_plugin_versions is a list of objects, where each object has `name` and `version` properties. If a plugin is loaded, and if an entry in `required_plugin_versions` has a matching name, then the loaded plugin version must be semver compatible with the version property.
Falco can load multiple rules files, and each file may contain its own `required_plugin_versions` property. In this case, name+version pairs across all files will be merged, and in the case of duplicate names all provided versions must be compatible.
### Loading the plugins
The mechanics of loading a plugin are implemented in the libraries and leverage the dynamic library functionality of the operating system (dlopen/dlsym in unix, LoadLibrary/GetProcAddress in Windows). The plugin loading code also ensures that:
- the plugin is valid, i.e. that it exports the set of expected symbols
- the plugin has an api version number that is compatible with the libraries instance
- that only one source plugin is loaded at a time for a given event source
- if a mix of source and extractor plugins are loaded for a given event source, that the exported fields have unique names that don't overlap across plugins
#### Loading plugins in falcosecurity/libs
At the libraries level, loading plugins is handled via the static method:
```c++
void sinsp_plugin::register_plugin(sinsp* inspector, string filepath, char* config, ...)
```
filepath points to a dynamic library containing code that exports plugin API functions. config contains arbitrary config content which is passed to init().
Note that the code using the libraries is responsible for determining the location of plugin libraries and their configuration.
#### Loading plugins in falcosecurity/falco
Falco will control/configure loading plugins via the new "plugins" property in falco.yaml. Here's an example:
```yaml
plugins:
- name: aws_cloudtrail
library_path: aws_cloudtrail/plugin.so
init_config: "..."
open_params: "..."
- name: http_json
library_path: http_json/plugin.so
init_config_file: http_json/config.txt
open_params_file: http_json/params.txt
# Optional
load_plugins: [aws_cloudtrail]
```
A new "plugins" property in falco.yaml will define the set of plugins that can be loaded by Falco. The property contains a list of objects, with the following properties:
* name: Only used for load_plugins, but by convention should be the same as the value returned by the name() api function.
* library_path: a path to the shared library. The path can be relative, in which case it is relative to Falco's "share" directory under a "plugins" subdirectory e.g. /usr/share/falco/plugins.
* init_config: If present, the exact configuration text that will be provided as an argument to the init() function.
* init_config_file: If present, the provided file will be read and the contents will be provided as an argument to the init() function.
* open_params: If present, the exact params text that will be provided as an argument to the open() function.
* open_params_file: If present, the provided file will be read and the contents will be provided as an argument to the open() function.
For a given yaml object in the plugins list, only one of init_config/init_config_file and one of open_params/open_params_file can be provided at a time.
A new "load_plugins" property in falco.yaml will allow for loading a subset of the plugins defined in plugins. If present, only the plugins with the provided names will be loaded.
### Examples
We have an initial version working, consisting of:
* A version of falcosecurity/libs that supports the [plugin framework](https://github.com/falcosecurity/libs/tree/new/plugin-system-api-additions)
* Support code and examples for [writing plugins in go](https://github.com/mstemm/libsinsp-plugin-sdk-go/tree/new/plugin-system-api-additions)
* A [cloudtrail](https://github.com/mstemm/plugins/tree/new/plugin-system-api-additions) plugin that can generate events from cloudtrail logs and extract fields from those events.
* A version of Falco that uses all of the above to [load and evaluate rules with plugins](https://github.com/leogr/falco/tree/new/plugin-system-api-additions)

View File

@@ -23,6 +23,7 @@ if(NOT DEFINED FALCO_RULES_DEST_FILENAME)
set(FALCO_LOCAL_RULES_DEST_FILENAME "falco_rules.local.yaml")
set(FALCO_APP_RULES_DEST_FILENAME "application_rules.yaml")
set(FALCO_K8S_AUDIT_RULES_DEST_FILENAME "k8s_audit_rules.yaml")
set(FALCO_AWS_CLOUDTRAIL_RULES_DEST_FILENAME "aws_cloudtrail_rules.yaml")
endif()
if(DEFINED FALCO_COMPONENT)
@@ -59,5 +60,10 @@ else()
DESTINATION "${FALCO_ETC_DIR}/rules.available"
RENAME "${FALCO_APP_RULES_DEST_FILENAME}")
install(
FILES aws_cloudtrail_rules.yaml
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_AWS_CLOUDTRAIL_RULES_DEST_FILENAME}")
install(DIRECTORY DESTINATION "${FALCO_ETC_DIR}/rules.d")
endif()

View File

@@ -0,0 +1,440 @@
#
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# All rules files related to plugins should require engine version 10
- required_engine_version: 10
# These rules can be read by cloudtrail plugin version 0.1.0, or
# anything semver-compatible.
- required_plugin_versions:
- name: cloudtrail
version: 0.1.0
# Note that this rule is disabled by default. It's useful only to
# verify that the cloudtrail plugin is sending events properly. The
# very broad condition evt.num > 0 only works because the rule source
# is limited to aws_cloudtrail. This ensures that the only events that
# are matched against the rule are from the cloudtrail plugin (or
# a different plugin with the same source).
- rule: All Cloudtrail Events
desc: Match all cloudtrail events.
condition:
evt.num > 0
output: Some Cloudtrail Event (evtnum=%evt.num info=%evt.plugininfo ts=%evt.time.iso8601 id=%ct.id error=%ct.error)
priority: DEBUG
tags:
- cloud
- aws
source: aws_cloudtrail
enabled: false
- rule: Console Login Through Assume Role
desc: Detect a console login through Assume Role.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and ct.user.identitytype="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
output:
Detected a console login through Assume Role
(principal=%ct.user.principalid,
assumedRole=%ct.user.arn,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: WARNING
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail
- rule: Console Login Without MFA
desc: Detect a console login without MFA.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and ct.user.identitytype!="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
and json.value[/additionalEventData/MFAUsed]="No"
output:
Detected a console login without MFA
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: CRITICAL
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail
- rule: Console Root Login Without MFA
desc: Detect root console login without MFA.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and json.value[/additionalEventData/MFAUsed]="No"
and ct.user.identitytype!="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
and ct.user.identitytype="Root"
output:
Detected a root console login without MFA.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: CRITICAL
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail
- rule: Deactivate MFA for Root User
desc: Detect deactivating MFA configuration for root.
condition:
ct.name="DeactivateMFADevice" and not ct.error exists
and ct.user.identitytype="Root"
and ct.request.username="AWS ROOT USER"
output:
Multi Factor Authentication configuration has been disabled for root
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
MFA serial number=%ct.request.serialnumber)
priority: CRITICAL
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: Create AWS user
desc: Detect creation of a new AWS user.
condition:
ct.name="CreateUser" and not ct.error exists
output:
A new AWS user has been created
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
new user created=%ct.request.username)
priority: INFO
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: Create Group
desc: Detect creation of a new user group.
condition:
ct.name="CreateGroup" and not ct.error exists
output:
A new user group has been created.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
group name=%ct.request.groupname)
priority: WARNING
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: Delete Group
desc: Detect deletion of a user group.
condition:
ct.name="DeleteGroup" and not ct.error exists
output:
A user group has been deleted.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
group name=%ct.request.groupname)
priority: WARNING
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: ECS Service Created
desc: Detect a new service is created in ECS.
condition:
ct.src="ecs.amazonaws.com" and
ct.name="CreateService" and
not ct.error exists
output:
A new service has been created in ECS
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
cluster=%ct.request.cluster,
service name=%ct.request.servicename,
task definition=%ct.request.taskdefinition)
priority: WARNING
tags:
- cloud
- aws
- aws_ecs
- aws_fargate
source: aws_cloudtrail
- rule: ECS Task Run or Started
desc: Detect a new task is started in ECS.
condition:
ct.src="ecs.amazonaws.com" and
(ct.name="RunTask" or ct.name="StartTask") and
not ct.error exists
output:
A new task has been started in ECS
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
cluster=%ct.request.cluster,
task definition=%ct.request.taskdefinition)
priority: WARNING
tags:
- cloud
- aws
- aws_ecs
- aws_fargate
source: aws_cloudtrail
- rule: Create Lambda Function
desc: Detect creation of a Lambda function.
condition:
ct.name="CreateFunction20150331" and not ct.error exists
output:
Lambda function has been created.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
lambda function=%ct.request.functionname)
priority: WARNING
tags:
- cloud
- aws
- aws_lambda
source: aws_cloudtrail
- rule: Update Lambda Function Code
desc: Detect updates to a Lambda function code.
condition:
ct.name="UpdateFunctionCode20150331v2" and not ct.error exists
output:
The code of a Lambda function has been updated.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
lambda function=%ct.request.functionname)
priority: WARNING
tags:
- cloud
- aws
- aws_lambda
source: aws_cloudtrail
- rule: Update Lambda Function Configuration
desc: Detect updates to a Lambda function configuration.
condition:
ct.name="UpdateFunctionConfiguration20150331v2" and not ct.error exists
output:
The configuration of a Lambda function has been updated.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
lambda function=%ct.request.functionname)
priority: WARNING
tags:
- cloud
- aws
- aws_lambda
source: aws_cloudtrail
- rule: Run Instances
desc: Detect launching of a specified number of instances.
condition:
ct.name="RunInstances" and not ct.error exists
output:
A number of instances have been launched.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
availability zone=%ct.request.availabilityzone,
subnet id=%ct.response.subnetid,
reservation id=%ct.response.reservationid)
priority: WARNING
tags:
- cloud
- aws
- aws_ec2
source: aws_cloudtrail
# Only instances launched on regions in this list are approved.
- list: approved_regions
items:
- us-east-0
- rule: Run Instances in Non-approved Region
desc: Detect launching of a specified number of instances in a non-approved region.
condition:
ct.name="RunInstances" and not ct.error exists and
not ct.region in (approved_regions)
output:
A number of instances have been launched in a non-approved region.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
availability zone=%ct.request.availabilityzone,
subnet id=%ct.response.subnetid,
reservation id=%ct.response.reservationid,
image id=%json.value[/responseElements/instancesSet/items/0/instanceId])
priority: WARNING
tags:
- cloud
- aws
- aws_ec2
source: aws_cloudtrail
- rule: Delete Bucket Encryption
desc: Detect deleting configuration to use encryption for bucket storage.
condition:
ct.name="DeleteBucketEncryption" and not ct.error exists
output:
A encryption configuration for a bucket has been deleted
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket=%s3.bucket)
priority: CRITICAL
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: Delete Bucket Public Access Block
desc: Detect deleting blocking public access to bucket.
condition:
ct.name="PutBucketPublicAccessBlock" and not ct.error exists and
json.value[/requestParameters/publicAccessBlock]="" and
(json.value[/requestParameters/PublicAccessBlockConfiguration/RestrictPublicBuckets]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/BlockPublicPolicy]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/BlockPublicAcls]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/IgnorePublicAcls]=false)
output:
A pulic access block for a bucket has been deleted
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket=%s3.bucket)
priority: CRITICAL
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: List Buckets
desc: Detect listing of all S3 buckets.
condition:
ct.name="ListBuckets" and not ct.error exists
output:
A list of all S3 buckets has been requested.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
host=%ct.request.host)
priority: WARNING
enabled: false
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: Put Bucket ACL
desc: Detect setting the permissions on an existing bucket using access control lists.
condition:
ct.name="PutBucketAcl" and not ct.error exists
output:
The permissions on an existing bucket have been set using access control lists.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket name=%s3.bucket)
priority: WARNING
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: Put Bucket Policy
desc: Detect applying an Amazon S3 bucket policy to an Amazon S3 bucket.
condition:
ct.name="PutBucketPolicy" and not ct.error exists
output:
An Amazon S3 bucket policy has been applied to an Amazon S3 bucket.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket name=%s3.bucket,
policy=%ct.request.policy)
priority: WARNING
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: CloudTrail Trail Created
desc: Detect creation of a new trail.
condition:
ct.name="CreateTrail" and not ct.error exists
output:
A new trail has been created.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
trail name=%ct.request.name)
priority: WARNING
tags:
- cloud
- aws
- aws_cloudtrail
source: aws_cloudtrail
- rule: CloudTrail Logging Disabled
desc: The CloudTrail logging has been disabled, this could be potentially malicious.
condition:
ct.name="StopLogging" and not ct.error exists
output:
The CloudTrail logging has been disabled.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
resource name=%ct.request.name)
priority: WARNING
tags:
- cloud
- aws
- aws_cloudtrail
source: aws_cloudtrail

View File

@@ -29,13 +29,13 @@
# condition: (syscall.type=read and evt.dir=> and fd.type in (file, directory))
- macro: open_write
condition: (evt.type=open or evt.type=openat) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0
condition: evt.type in (open,openat,openat2) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0
- macro: open_read
condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='f' and fd.num>=0
condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='f' and fd.num>=0
- macro: open_directory
condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='d' and fd.num>=0
condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='d' and fd.num>=0
- macro: never_true
condition: (evt.num=0)
@@ -220,7 +220,7 @@
]
# The truncated dpkg-preconfigu is intentional, process names are
# truncated at the sysdig level.
# truncated at the falcosecurity-libs level.
- list: package_mgmt_binaries
items: [rpm_binaries, deb_binaries, update-alternat, gem, pip, pip3, sane-utils.post, alternatives, chef-client, apk, snapd]
@@ -333,7 +333,7 @@
# for efficiency.
- macro: inbound_outbound
condition: >
((((evt.type in (accept,listen,connect) and evt.dir=<)) or
((((evt.type in (accept,listen,connect) and evt.dir=<)) and
(fd.typechar = 4 or fd.typechar = 6)) and
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
(evt.rawres >= 0 or evt.res = EINPROGRESS))
@@ -1755,7 +1755,6 @@
(container.image.repository startswith "602401143452.dkr.ecr" or
container.image.repository startswith "877085696533.dkr.ecr" or
container.image.repository startswith "800184023465.dkr.ecr" or
container.image.repository startswith "602401143452.dkr.ecr" or
container.image.repository startswith "918309763551.dkr.ecr" or
container.image.repository startswith "961992271922.dkr.ecr" or
container.image.repository startswith "590381155156.dkr.ecr" or
@@ -1827,6 +1826,7 @@
k8s.gcr.io/ip-masq-agent-amd64,
k8s.gcr.io/kube-proxy,
k8s.gcr.io/prometheus-to-sd,
public.ecr.aws/falcosecurity/falco,
quay.io/calico/node,
sysdig/sysdig,
sematext_images
@@ -1855,7 +1855,7 @@
- list: falco_sensitive_mount_images
items: [
docker.io/sysdig/sysdig, sysdig/sysdig,
docker.io/falcosecurity/falco, falcosecurity/falco,
docker.io/falcosecurity/falco, falcosecurity/falco, public.ecr.aws/falcosecurity/falco,
gcr.io/google_containers/hyperkube,
gcr.io/google_containers/kube-proxy, docker.io/calico/node,
docker.io/rook/toolbox, docker.io/cloudnativelabs/kube-router, docker.io/consul,
@@ -1906,12 +1906,13 @@
# For now, only considering a full mount of /etc as
# sensitive. Ideally, this would also consider all subdirectories
# below /etc as well, but the globbing mechanism used by sysdig
# below /etc as well, but the globbing mechanism
# doesn't allow exclusions of a full pattern, only single characters.
- macro: sensitive_mount
condition: (container.mount.dest[/proc*] != "N/A" or
container.mount.dest[/var/run/docker.sock] != "N/A" or
container.mount.dest[/var/run/crio/crio.sock] != "N/A" or
container.mount.dest[/run/containerd/containerd.sock] != "N/A" or
container.mount.dest[/var/lib/kubelet] != "N/A" or
container.mount.dest[/var/lib/kubelet/pki] != "N/A" or
container.mount.dest[/] != "N/A" or
@@ -2296,7 +2297,7 @@
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 or evt.type = openat) and evt.arg.flags contains O_CREAT))
(evt.type = creat or (evt.type in (open,openat,openat2) and evt.arg.flags contains O_CREAT))
and not proc.name in (dev_creation_binaries)
and not fd.name in (allowed_dev_files)
and not fd.name startswith /dev/tty
@@ -2364,7 +2365,8 @@
docker.io/sysdig/sysdig, docker.io/falcosecurity/falco,
sysdig/sysdig, falcosecurity/falco,
fluent/fluentd-kubernetes-daemonset, prom/prometheus,
ibm_cloud_containers)
ibm_cloud_containers,
public.ecr.aws/falcosecurity/falco)
or (k8s.ns.name = "kube-system"))
- macro: k8s_api_server
@@ -2706,7 +2708,7 @@
create_symlink and
(evt.arg.target in (sensitive_file_names) or evt.arg.target in (sensitive_directory_names))
output: >
Symlinks created over senstivie files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname)
Symlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname)
priority: NOTICE
tags: [file, mitre_exfiltration]
@@ -2747,7 +2749,12 @@
"xmr-eu1.nanopool.org","xmr-eu2.nanopool.org",
"xmr-jp1.nanopool.org","xmr-us-east1.nanopool.org",
"xmr-us-west1.nanopool.org","xmr.crypto-pool.fr",
"xmr.pool.minergate.com", "rx.unmineable.com"
"xmr.pool.minergate.com", "rx.unmineable.com",
"ss.antpool.com","dash.antpool.com",
"eth.antpool.com","zec.antpool.com",
"xmc.antpool.com","btm.antpool.com",
"stratum-dash.antpool.com","stratum-xmc.antpool.com",
"stratum-btm.antpool.com"
]
- list: https_miner_domains
@@ -2764,7 +2771,12 @@
"stratum-ltc.antpool.com",
"stratum-zec.antpool.com",
"stratum.antpool.com",
"xmr.crypto-pool.fr"
"xmr.crypto-pool.fr",
"ss.antpool.com",
"stratum-dash.antpool.com",
"stratum-xmc.antpool.com",
"stratum-btm.antpool.com",
"btm.antpool.com"
]
- list: http_miner_domains
@@ -2793,7 +2805,7 @@
condition: (evt.type in (sendto, sendmsg) and evt.dir=< and (fd.net != "127.0.0.0/8" and not fd.snet in (rfc_1918_addresses)) and ((minerpool_http) or (minerpool_https) or (minerpool_other)))
- macro: trusted_images_query_miner_domain_dns
condition: (container.image.repository in (docker.io/falcosecurity/falco, falcosecurity/falco))
condition: (container.image.repository in (docker.io/falcosecurity/falco, falcosecurity/falco, public.ecr.aws/falcosecurity/falco))
append: false
# The rule is disabled by default.
@@ -2944,7 +2956,7 @@
# The two Container Drift rules below will fire when a new executable is created in a container.
# There are two ways to create executables - file is created with execution permissions or permissions change of existing file.
# We will use a new sysdig filter, is_open_exec, to find all files creations with execution permission, and will trace all chmods in a container.
# We will use a new filter, is_open_exec, to find all files creations with execution permission, and will trace all chmods in a container.
# The use case we are targeting here is an attempt to execute code that was not shipped as part of a container (drift) -
# an activity that might be malicious or non-compliant.
# Two things to pay attention to:
@@ -2977,7 +2989,7 @@
- rule: Container Drift Detected (open+create)
desc: New executable created in a container due to open+create
condition: >
evt.type in (open,openat,creat) and
evt.type in (open,openat,openat2,creat) and
evt.is_open_exec=true and
container and
not runc_writing_exec_fifo and
@@ -3074,6 +3086,40 @@
priority: CRITICAL
tags: [syscall, mitre_defense_evasion]
- list: ingress_remote_file_copy_binaries
items: [wget]
- macro: ingress_remote_file_copy_procs
condition: (proc.name in (ingress_remote_file_copy_binaries))
# Users should overwrite this macro to specify conditions under which a
# Custom condition for use of ingress remote file copy tool in container
- macro: user_known_ingress_remote_file_copy_activities
condition: (never_true)
- macro: curl_download
condition: proc.name = curl and
(proc.cmdline contains " > " or
proc.cmdline contains " >> " or
proc.cmdline contains " | " or
proc.cmdline contains " -o " or
proc.cmdline contains " --output " or
proc.cmdline contains " -O " or
proc.cmdline contains " --remote-name ")
- rule: Launch Ingress Remote File Copy Tools in Container
desc: Detect ingress remote file copy tools launched in container
condition: >
spawned_process and
container and
(ingress_remote_file_copy_procs or curl_download) and
not user_known_ingress_remote_file_copy_activities
output: >
Ingress remote file copy tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent_process=%proc.pname
container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, process, mitre_command_and_control]
# Application rules have moved to application_rules.yaml. Please look
# there if you want to enable them by adding to
# falco_rules.local.yaml.

View File

@@ -302,9 +302,31 @@
items: []
- list: known_sa_list
items: ["pod-garbage-collector","resourcequota-controller","cronjob-controller","generic-garbage-collector",
"daemon-set-controller","endpointslice-controller","deployment-controller", "replicaset-controller",
"endpoint-controller", "namespace-controller", "statefulset-controller", "disruption-controller"]
items: [
coredns,
coredns-autoscaler,
cronjob-controller,
daemon-set-controller,
deployment-controller,
disruption-controller,
endpoint-controller,
endpointslice-controller,
endpointslicemirroring-controller,
generic-garbage-collector,
horizontal-pod-autoscaler,
job-controller,
namespace-controller,
node-controller,
persistent-volume-binder,
pod-garbage-collector,
pv-protection-controller,
pvc-protection-controller,
replicaset-controller,
resourcequota-controller,
root-ca-cert-publisher,
service-account-controller,
statefulset-controller
]
- macro: trusted_sa
condition: (ka.target.name in (known_sa_list, user_known_sa_list))

View File

@@ -154,13 +154,15 @@ load_kernel_module_compile() {
fi
# Try to compile using all the available gcc versions
for CURRENT_GCC in $(which gcc) $(ls "$(dirname "$(which gcc)")"/gcc-* | grep 'gcc-[0-9]\+' | sort -r); do
for CURRENT_GCC in $(which gcc) $(ls "$(dirname "$(which gcc)")"/gcc-* | grep 'gcc-[0-9]\+' | sort -n -r -k 2 -t -); do
echo "* Trying to dkms install ${DRIVER_NAME} module with GCC ${CURRENT_GCC}"
echo "#!/usr/bin/env bash" > /tmp/falco-dkms-make
echo "make CC=${CURRENT_GCC} \$@" >> /tmp/falco-dkms-make
chmod +x /tmp/falco-dkms-make
if dkms install --directive="MAKE='/tmp/falco-dkms-make'" -m "${DRIVER_NAME}" -v "${DRIVER_VERSION}" -k "${KERNEL_RELEASE}" 2>/dev/null; then
echo "* ${DRIVER_NAME} module installed in dkms, trying to insmod"
chcon -t modules_object_t "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko" > /dev/null 2>&1 || true
chcon -t modules_object_t "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko.xz" > /dev/null 2>&1 || true
if insmod "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko" > /dev/null 2>&1; then
echo "* Success: ${DRIVER_NAME} module found and loaded in dkms"
exit 0
@@ -193,6 +195,7 @@ load_kernel_module_download() {
echo "* Trying to download a prebuilt ${DRIVER_NAME} module from ${URL}"
if curl -L --create-dirs "${FALCO_DRIVER_CURL_OPTIONS}" -o "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" "${URL}"; then
echo "* Download succeeded"
chcon -t modules_object_t "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true
insmod "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" && echo "* Success: ${DRIVER_NAME} module found and inserted"
exit $?
else
@@ -252,6 +255,7 @@ load_kernel_module() {
if [ -f "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" ]; then
echo "* Found a prebuilt ${DRIVER_NAME} module at ${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}, loading it"
chcon -t modules_object_t "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true
insmod "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" && echo "* Success: ${DRIVER_NAME} module found and inserted"
exit $?
fi
@@ -296,16 +300,16 @@ clean_kernel_module() {
return
fi
DRIVER_VERSIONS=$(dkms status -m "${DRIVER_NAME}" | cut -d',' -f2 | sed -e 's/^[[:space:]]*//')
DRIVER_VERSIONS=$(dkms status -m "${DRIVER_NAME}" | cut -d',' -f1 | sed -e 's/^[[:space:]]*//')
if [ -z "${DRIVER_VERSIONS}" ]; then
echo "* There is no ${DRIVER_NAME} module in dkms"
return
fi
for CURRENT_VER in ${DRIVER_VERSIONS}; do
if dkms remove -m "${DRIVER_NAME}" -v "${CURRENT_VER}" --all 2>/dev/null; then
echo "* Removing ${DRIVER_NAME}/${CURRENT_VER} succeeded"
if dkms remove "${CURRENT_VER}" --all 2>/dev/null; then
echo "* Removing ${CURRENT_VER} succeeded"
else
echo "* Removing ${DRIVER_NAME}/${CURRENT_VER} failed"
echo "* Removing ${CURRENT_VER} failed"
exit 1
fi
done
@@ -634,4 +638,4 @@ if [ -z "$source_only" ]; then
;;
esac
fi
fi
fi

View File

@@ -23,7 +23,7 @@ add_deb() {
cp -f $3 $1/$2
pushd $1/$2 > /dev/null
rm -f $(basename -- $3).asc
gpg --detach-sign --armor $(basename -- $3)
gpg --detach-sign --digest-algo SHA256 --armor $(basename -- $3)
popd > /dev/null
}
@@ -63,7 +63,7 @@ update_repo() {
${release_dir} > ${release_dir}/Release
# release signature
gpg --detach-sign --armor ${release_dir}/Release
gpg --detach-sign --digest-algo SHA256 --armor ${release_dir}/Release
rm -f ${release_dir}/Release.gpg
mv ${release_dir}/Release.asc ${release_dir}/Release.gpg
@@ -129,4 +129,4 @@ aws s3 sync ${tmp_repo_path}/dists ${s3_bucket_repo}/dists --delete --acl public
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}.asc
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/dists/*
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/dists/*

View File

@@ -22,7 +22,7 @@ add_rpm() {
cp -f $2 $1
pushd $1 > /dev/null
rm -f $(basename -- $2).asc
gpg --detach-sign --armor $(basename -- $2)
gpg --detach-sign --digest-algo SHA256 --armor $(basename -- $2)
popd > /dev/null
}
@@ -33,7 +33,7 @@ update_repo() {
pushd $1 > /dev/null
createrepo --update --no-database .
rm -f repodata/repomd.xml.asc
gpg --detach-sign --armor repodata/repomd.xml
gpg --detach-sign --digest-algo SHA256 --armor repodata/repomd.xml
popd > /dev/null
}
@@ -93,4 +93,4 @@ aws s3 sync ${tmp_repo_path}/repodata ${s3_bucket_repo}/repodata --delete --acl
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${package}
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${package}.asc
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/repodata/*
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/repodata/*

View File

@@ -19,6 +19,7 @@ ProtectSystem=full
ProtectKernelTunables=true
RestrictRealtime=true
RestrictAddressFamilies=~AF_PACKET
StandardOutput=null
[Install]
WantedBy=multi-user.target

View File

@@ -1,4 +1,7 @@
add_subdirectory(trace_files)
add_custom_target(test-trace-files ALL)
add_dependencies(test-trace-files trace-files-base-scap trace-files-psp trace-files-k8s-audit)
add_dependencies(test-trace-files trace-files-base-scap trace-files-psp trace-files-k8s-audit trace-files-plugins)
add_subdirectory(plugins)
add_subdirectory(confs/plugins)

View File

@@ -0,0 +1,16 @@
# This list is populated at cmake time, not build time
file(GLOB test_conf_files
"${CMAKE_CURRENT_SOURCE_DIR}/*.yaml")
foreach(conf_file_path ${test_conf_files})
get_filename_component(conf_file ${conf_file_path} NAME)
add_custom_target(test-conf-${conf_file} ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${conf_file})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${conf_file}
COMMAND sed -e s!BUILD_DIR!${CMAKE_BINARY_DIR}! < ${CMAKE_CURRENT_SOURCE_DIR}/${conf_file} > ${CMAKE_CURRENT_BINARY_DIR}/${conf_file}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${conf_file})
list(APPEND PLUGINS_CONF_FILES_TARGETS test-conf-${conf_file})
endforeach()
add_custom_target(conf-files-plugins ALL)
add_dependencies(conf-files-plugins ${PLUGINS_CONF_FILES_TARGETS})

View File

@@ -0,0 +1,14 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: "BUILD_DIR/test/trace_files/plugins/alice_start_instances.json"
- name: json
library_path: BUILD_DIR/json-plugin-prefix/src/json-plugin/libjson.so
init_config: ""
# Optional
load_plugins: [cloudtrail, json]

View File

@@ -0,0 +1,14 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: "BUILD_DIR/test/trace_files/plugins/alice_start_instances_bigevent.json"
- name: json
library_path: BUILD_DIR/json-plugin-prefix/src/json-plugin/libjson.so
init_config: ""
# Optional
load_plugins: [cloudtrail, json]

View File

@@ -0,0 +1,14 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: ""
- name: test_extract_p1
library_path: BUILD_DIR/test/plugins/libtest_extract_p1.so
init_config: ""
# Optional
load_plugins: [cloudtrail, test_extract_p1]

View File

@@ -0,0 +1,10 @@
stdout_output:
enabled: true
plugins:
- name: incompatible_plugin_api
library_path: BUILD_DIR/test/plugins/libtest_incompat_api.so
init_config: ""
# Optional
load_plugins: [incompatible_plugin_api]

View File

@@ -0,0 +1,15 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: "BUILD_DIR/test/trace_files/plugins/alice_start_instances.json"
- name: test_source
library_path: BUILD_DIR/test/plugins/libtest_source.so
init_config: ""
open_params: ""
# Optional
load_plugins: [cloudtrail, test_source]

View File

@@ -0,0 +1,17 @@
stdout_output:
enabled: true
plugins:
- name: test_source
library_path: BUILD_DIR/test/plugins/libtest_source.so
init_config: ""
open_params: ""
- name: test_extract_p1
library_path: BUILD_DIR/test/plugins/libtest_extract_p1.so
init_config: ""
- name: test_extract_p2
library_path: BUILD_DIR/test/plugins/libtest_extract_p2.so
init_config: ""
# Optional
load_plugins: [test_source, test_extract_p1, test_extract_p2]

View File

@@ -0,0 +1,10 @@
stdout_output:
enabled: true
plugins:
- name: wrong_plugin_path
library_path: BUILD_DIR/test/plugins/wrong_plugin_path.so
init_config: ""
# Optional
load_plugins: [wrong_plugin_path]

View File

@@ -43,6 +43,12 @@ json_output: false
# (user=root ....") in the json output.
json_include_output_property: true
# When using json output, whether or not to include the "tags" property
# itself in the json output. If set to true, outputs caused by rules
# with no tags will have a "tags" field set to an empty array. If set to
# false, the "tags" field will not be included in the json output at all.
json_include_tags_property: true
# 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: true

View File

@@ -632,3 +632,12 @@ trace_files: !mux
- ../rules/k8s_audit_rules.yaml
trace_file: trace_files/k8s_audit/fal_01_003.json
stderr_contains: 'Could not read k8s audit event line #1, "{"kind": 0}": Data not recognized as a k8s audit event, stopping'
json_pointer_correct_parse:
detect: True
detect_level: WARNING
rules_file:
- ./rules/k8s_audit/single_rule_with_json_pointer.yaml
detect_counts:
- json_pointer_example: 1
trace_file: trace_files/k8s_audit/create_nginx_pod_unprivileged.json

View File

@@ -82,6 +82,7 @@ class FalcoTest(Test):
self.exit_status = self.params.get('exit_status', '*', default=0)
self.should_detect = self.params.get('detect', '*', default=False)
self.check_detection_counts = self.params.get('check_detection_counts', '*', default=True)
self.trace_file = self.params.get('trace_file', '*', default='')
if self.trace_file and not os.path.isabs(self.trace_file):
@@ -90,8 +91,11 @@ class FalcoTest(Test):
self.json_output = self.params.get('json_output', '*', default=False)
self.json_include_output_property = self.params.get(
'json_include_output_property', '*', default=True)
self.json_include_tags_property = self.params.get(
'json_include_tags_property', '*', default=True)
self.all_events = self.params.get('all_events', '*', default=False)
self.priority = self.params.get('priority', '*', default='debug')
self.addl_cmdline_opts = self.params.get('addl_cmdline_opts', '*', default='')
self.rules_file = self.params.get(
'rules_file', '*', default=os.path.join(self.basedir, '../rules/falco_rules.yaml'))
@@ -128,6 +132,7 @@ class FalcoTest(Test):
self.conf_file = self.params.get(
'conf_file', '*', default=os.path.join(self.basedir, '../falco.yaml'))
self.conf_file = self.conf_file.replace("BUILD_DIR", build_dir)
if not os.path.isabs(self.conf_file):
self.conf_file = os.path.join(self.basedir, self.conf_file)
@@ -388,10 +393,11 @@ class FalcoTest(Test):
for line in res.stdout.decode("utf-8").splitlines():
if line.startswith('{'):
obj = json.loads(line)
attrs = ['time', 'rule', 'priority']
if self.json_include_output_property:
attrs = ['time', 'rule', 'priority', 'output']
else:
attrs = ['time', 'rule', 'priority']
attrs.append('output')
if self.json_include_tags_property:
attrs.append('tags')
for attr in attrs:
if not attr in obj:
self.fail(
@@ -409,10 +415,15 @@ class FalcoTest(Test):
else:
actual = open(output['actual']).read()
if expected not in actual:
self.fail("Output '{}' does not strictly contains the expected content '{}'".format(
output['actual'], output['expected']))
return False
actual_cursor = actual
expected_lines = expected.splitlines()
for line in expected_lines:
pos = actual_cursor.find(line)
if pos < 0:
self.fail("Output '{}' does not strictly contains the expected content '{}'".format(
output['actual'], output['expected']))
return False
actual_cursor = actual_cursor[pos + len(line):]
return True
@@ -609,8 +620,9 @@ class FalcoTest(Test):
self.log.debug("Converted Rules: {}".format(psp_rules))
# Run falco
cmd = '{} {} {} -c {} {} -o json_output={} -o json_include_output_property={} -o priority={} -v'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output, self.json_include_output_property, self.priority)
cmd = '{} {} {} -c {} {} -o json_output={} -o json_include_output_property={} -o json_include_tags_property={} -o priority={} -v {}'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output,
self.json_include_output_property, self.json_include_tags_property, self.priority, self.addl_cmdline_opts)
for tag in self.disable_tags:
cmd += ' -T {}'.format(tag)
@@ -641,13 +653,13 @@ class FalcoTest(Test):
self.fail("Stdout was not exactly {}".format(self.stderr_is))
for pattern in self.stderr_contains:
match = re.search(pattern, res.stderr.decode("utf-8"))
match = re.search(pattern, res.stderr.decode("utf-8"), re.DOTALL)
if match is None:
self.fail(
"Stderr of falco process did not contain content matching {}".format(pattern))
for pattern in self.stdout_contains:
match = re.search(pattern, res.stdout.decode("utf-8"))
match = re.search(pattern, res.stdout.decode("utf-8"), re.DOTALL)
if match is None:
self.fail("Stdout of falco process '{}' did not contain content matching {}".format(
res.stdout.decode("utf-8"), pattern))
@@ -675,7 +687,7 @@ class FalcoTest(Test):
self.check_rules_warnings(res)
if len(self.rules_events) > 0:
self.check_rules_events(res)
if len(self.validate_rules_file) == 0:
if len(self.validate_rules_file) == 0 and self.check_detection_counts:
self.check_detections(res)
if len(self.detect_counts) > 0:
self.check_detections_by_rule(res)

View File

@@ -32,20 +32,10 @@ trace_files: !mux
- leading_not
- not_equals_at_end
- not_at_end
- not_before_trailing_evttype
- not_equals_before_trailing_evttype
- not_equals_and_not
- not_equals_before_in
- not_before_in
- not_in_before_in
- leading_in_not_equals_before_evttype
- leading_in_not_equals_at_evttype
- not_with_evttypes
- not_with_evttypes_addl
- not_equals_before_evttype
- not_equals_before_in_evttype
- not_before_evttype
- not_before_evttype_using_in
rules_events:
- no_warnings: [execve]
- no_evttype: [all]
@@ -364,7 +354,7 @@ trace_files: !mux
exit_status: 1
stdout_is: |+
1 errors:
Rule must have exceptions or condition property
Appended rule must have exceptions or condition property
---
- rule: no condition rule
append: true
@@ -644,6 +634,20 @@ trace_files: !mux
rules_file:
- rules/single_rule_enabled_flag.yaml
trace_file: trace_files/cat_write.scap
disabled_rule_using_false_enabled_flag_only:
detect: False
rules_file:
- rules/disabled_rule_using_enabled_flag_only.yaml
trace_file: trace_files/cat_write.scap
enabled_rule_using_false_enabled_flag_only:
detect: True
detect_level: WARNING
rules_file:
- rules/enabled_rule_using_enabled_flag_only.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "Warning An open was seen"
disabled_and_enabled_rules_1:
exit_status: 1
@@ -688,7 +692,7 @@ trace_files: !mux
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule.yaml
- rules/single_rule_with_tags.yaml
conf_file: confs/stdout_output.yaml
trace_file: trace_files/cat_write.scap
time_iso_8601: true
@@ -721,7 +725,7 @@ trace_files: !mux
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule.yaml
- rules/single_rule_with_tags.yaml
conf_file: confs/grpc_unix_socket.yaml
trace_file: trace_files/cat_write.scap
run_duration: 5
@@ -745,6 +749,10 @@ trace_files: !mux
# For the hostname, since we don't know that beforehand,
# only check the field presence
- "hostname: "
#tags
- "tags: \"filesystem\""
- "tags: \"process\""
- "tags: \"testing\""
detect_counts:
detect: True
@@ -1107,6 +1115,25 @@ trace_files: !mux
trace_file: trace_files/cat_write.scap
stdout_contains: "^(?!.*Warning An open of /dev/null was seen.*)"
json_output_no_tags_property:
json_output: True
json_include_tags_property: False
detect: True
detect_level: WARNING
rules_file:
- rules/rule_append.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "^(?!.*\"tags\":[ ]*\\[.*\\],.*)"
json_output_empty_tags_property:
json_output: True
detect: True
detect_level: WARNING
rules_file:
- rules/rule_append.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "^(.*\"tags\":[ ]*\\[\\],.*)"
in_operator_netmasks:
detect: True
detect_level: INFO
@@ -1119,6 +1146,8 @@ trace_files: !mux
detect_level: INFO
rules_file:
- rules/syscalls.yaml
rules_warning:
- detect_madvise
detect_counts:
- detect_madvise: 2
- detect_open: 2
@@ -1137,7 +1166,8 @@ trace_files: !mux
skip_unknown_noevt:
detect: False
stdout_contains: Skipping rule "Contains Unknown Event And Skipping". contains unknown filter proc.nobody
rules_warning:
- Contains Unknown Event And Skipping
rules_file:
- rules/skip_unknown_evt.yaml
trace_file: trace_files/cat_write.scap
@@ -1152,7 +1182,7 @@ trace_files: !mux
exit_status: 1
stderr_contains: |+
Could not load rules file.*skip_unknown_error.yaml: 1 errors:
rule "Contains Unknown Event And Not Skipping". contains unknown filter proc.nobody
Rule Contains Unknown Event And Not Skipping: error filter_check called with nonexistent field proc.nobody
---
- rule: Contains Unknown Event And Not Skipping
desc: Contains an unknown event
@@ -1169,7 +1199,7 @@ trace_files: !mux
exit_status: 1
stderr_contains: |+
Could not load rules file .*skip_unknown_unspec.yaml: 1 errors:
rule "Contains Unknown Event And Unspecified". contains unknown filter proc.nobody
Rule Contains Unknown Event And Unspecified: error filter_check called with nonexistent field proc.nobody
---
- rule: Contains Unknown Event And Unspecified
desc: Contains an unknown event

View File

@@ -177,10 +177,18 @@ trace_files: !mux
trace_file: trace_files/cat_write.scap
rule_exception_append_item_not_in_rule:
exit_status: 0
stderr_contains: |+
1 warnings:
Rule My Rule with append=true: no set of fields matching name ex2
exit_status: 1
stdout_is: |+
1 errors:
Rule exception new item ex2: must have fields property with a list of fields
---
- rule: My Rule
exceptions:
- name: ex2
values:
- [apache, /tmp]
append: true
---
validate_rules_file:
- rules/exceptions/append_item_not_in_rule.yaml
trace_file: trace_files/cat_write.scap
@@ -311,4 +319,35 @@ trace_files: !mux
- rules/exceptions/rule_exception_single_field_append.yaml
trace_file: trace_files/cat_write.scap
rule_exception_new_single_field_append:
detect: False
detect_level: WARNING
rules_file:
- rules/exceptions/rule_exception_new_single_field_append.yaml
trace_file: trace_files/cat_write.scap
rule_exception_new_second_field_append:
detect: False
detect_level: WARNING
rules_file:
- rules/exceptions/rule_exception_new_second_field_append.yaml
trace_file: trace_files/cat_write.scap
rule_exception_new_append_no_field:
exit_status: 1
stdout_is: |+
1 errors:
Rule exception new item proc_cmdline: must have fields property with a list of fields
---
- rule: Open From Cat
exceptions:
- name: proc_cmdline
comps: in
values:
- "cat /dev/null"
append: true
---
validate_rules_file:
- rules/exceptions/rule_exception_new_no_field_append.yaml
trace_file: trace_files/cat_write.scap

View File

@@ -0,0 +1,106 @@
#
# Copyright (C) 2021 The Falco Authors.
#
# This file is part of Falco.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
trace_files: !mux
list_plugins:
check_detection_counts: False
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
addl_cmdline_opts: --list-plugins
stdout_contains: "2 Plugins Loaded.*Name: cloudtrail.*Type: source plugin.*Name: json.*Type: extractor plugin"
list_plugin_fields:
check_detection_counts: False
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
addl_cmdline_opts: --list
stdout_contains: "ct.id"
detect_create_instance:
detect: True
detect_level: INFO
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
detect_counts:
- 'Cloudtrail Create Instance': 1
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
detect_create_instance_bigevent:
detect: True
detect_level: INFO
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
detect_counts:
- 'Cloudtrail Create Instance': 1
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances_bigevent.yaml
multiple_source_plugins:
exit_status: 1
stderr_contains: "Runtime error: Can not load multiple source plugins. cloudtrail already loaded. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/multiple_source_plugins.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
incompatible_extract_sources:
exit_status: 1
stderr_contains: "Runtime error: Extractor plugin not compatible with event source aws_cloudtrail. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/incompatible_extract_sources.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
overlap_extract_sources:
exit_status: 1
stderr_contains: "Runtime error: Extractor plugins have overlapping compatible event source test_source. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/overlap_extract_sources.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
incompat_plugin_api:
exit_status: 1
stderr_contains: "Unsupported plugin required api version 10000000.0.0"
conf_file: BUILD_DIR/test/confs/plugins/incompatible_plugin_api.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
incompat_plugin_rules_version:
exit_status: 1
stderr_contains: "Runtime error: Plugin cloudtrail version 0.1.0 not compatible with required plugin version 100000.0.0. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
rules_file:
- rules/plugins/cloudtrail_incompat_plugin_version.yaml
wrong_plugin_path:
exit_status: 1
stderr_contains: "error loading plugin.*No such file or directory. Exiting"
conf_file: BUILD_DIR/test/confs/plugins/wrong_plugin_path.yaml
rules_file:
- rules/plugins/cloudtrail_incompat_plugin_version.yaml
no_plugins_unknown_source:
detect: False
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
trace_file: trace_files/empty.scap
rules_warning:
- Cloudtrail Create Instance
stderr_contains: "Rule Cloudtrail Create Instance: warning .unknown-source.: unknown source aws_cloudtrail, skipping"

View File

@@ -111,12 +111,17 @@ traces: !mux
detect_counts:
- "Read sensitive file untrusted": 1
# This should *not* generate any falco alerts as of the changes in
# https://github.com/falcosecurity/libs/pull/94--the execve event in
# this trace file is PPME_SYSCALL_EXECVE_18, which was deprecated by
# PPME_SYSCALL_EXECVE_19 in 2018.
#
# This activity in this trace file overlaps with the activity in
# falco-event-generator.scap so the rule is still being tested.
run-shell-untrusted:
trace_file: traces-positive/run-shell-untrusted.scap
detect: True
detect: False
detect_level: DEBUG
detect_counts:
- "Run shell untrusted": 1
system-binaries-network-activity:
trace_file: traces-positive/system-binaries-network-activity.scap

View File

@@ -1,8 +1,8 @@
{"output":"2016-08-04T16:17:57.881781397+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881781397Z", "output_fields": {"evt.time.iso8601":1470327477881781397,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881785348+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881785348Z", "output_fields": {"evt.time.iso8601":1470327477881785348,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881796705+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881796705Z", "output_fields": {"evt.time.iso8601":1470327477881796705,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881799840+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881799840Z", "output_fields": {"evt.time.iso8601":1470327477881799840,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882003104+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882003104Z", "output_fields": {"evt.time.iso8601":1470327477882003104,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882008208+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882008208Z", "output_fields": {"evt.time.iso8601":1470327477882008208,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882045694+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882045694Z", "output_fields": {"evt.time.iso8601":1470327477882045694,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882054739+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882054739Z", "output_fields": {"evt.time.iso8601":1470327477882054739,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881781397+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881781397Z", "output_fields": {"evt.time.iso8601":1470327477881781397,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881785348+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881785348Z", "output_fields": {"evt.time.iso8601":1470327477881785348,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881796705+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881796705Z", "output_fields": {"evt.time.iso8601":1470327477881796705,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881799840+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881799840Z", "output_fields": {"evt.time.iso8601":1470327477881799840,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882003104+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882003104Z", "output_fields": {"evt.time.iso8601":1470327477882003104,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882008208+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882008208Z", "output_fields": {"evt.time.iso8601":1470327477882008208,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882045694+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882045694Z", "output_fields": {"evt.time.iso8601":1470327477882045694,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882054739+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882054739Z", "output_fields": {"evt.time.iso8601":1470327477882054739,"proc.cmdline":"cat /dev/null"}}

View File

@@ -1,69 +0,0 @@
#
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require(jsonlite)
library(ggplot2)
library(GetoptLong)
initial.options <- commandArgs(trailingOnly = FALSE)
file.arg.name <- "--file="
script.name <- sub(file.arg.name, "", initial.options[grep(file.arg.name, initial.options)])
script.basename <- dirname(script.name)
if (substr(script.basename, 1, 1) != '/') {
script.basename = paste(getwd(), script.basename, sep='/')
}
results = paste(script.basename, "results.json", sep='/')
output = "./output.png"
metric = "cpu"
GetoptLong(
"results=s", "Path to results file",
"benchmark=s", "Benchmark from results file to graph",
"variant=s@", "Variant(s) to include in graph. Can be specified multiple times",
"output=s", "Output graph file",
"metric=s", "Metric to graph. Can be one of (cpu|drops)"
)
if (metric == "cpu") {
data_metric="cpu_usage"
yaxis_label="CPU Usage (%)"
title="Falco/Sysdig/Multimatch CPU Usage: %s"
} else if (metric == "drops") {
data_metric="drop_pct"
yaxis_label="Event Drops (%)"
title="Falco/Sysdig/Multimatch Event Drops: %s"
}
res <- fromJSON(results, flatten=TRUE)
res2 = res[res$benchmark == benchmark & res$variant %in% variant,]
plot <- ggplot(data=res2, aes(x=sample, y=get(data_metric), group=variant, colour=variant)) +
geom_line() +
ylab(yaxis_label) +
xlab("Time") +
ggtitle(sprintf(title, benchmark))
theme(legend.position=c(.2, .88));
print(paste("Writing graph to", output, sep=" "))
ggsave(file=output)

View File

@@ -1,52 +0,0 @@
#
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require(jsonlite)
library(ggplot2)
library(reshape)
res <- fromJSON("/home/mstemm/results.txt", flatten=TRUE)
plot <- ggplot(data=res, aes(x=config, y=elapsed.real)) +
geom_bar(stat = "summary", fun.y = "mean") +
coord_flip() +
facet_grid(shortfile ~ .) +
ylab("Wall Clock Time (sec)") +
xlab("Trace File/Program")
ggsave(file="/mnt/sf_mstemm/res-real.png")
plot <- ggplot(data=res, aes(x=config, y=elapsed.user)) +
geom_bar(stat = "summary", fun.y = "mean") +
coord_flip() +
facet_grid(shortfile ~ .) +
ylab("User Time (sec)") +
xlab("Trace File/Program")
ggsave(file="/mnt/sf_mstemm/res-user.png")
res2 <- melt(res, id.vars = c("config", "shortfile"), measure.vars = c("elapsed.sys", "elapsed.user"))
plot <- ggplot(data=res2, aes(x=config, y=value, fill=variable, order=variable)) +
geom_bar(stat = "summary", fun.y = "mean") +
coord_flip() +
facet_grid(shortfile ~ .) +
ylab("User/System Time (sec)") +
xlab("Trace File/Program")
ggsave(file="/mnt/sf_mstemm/res-sys-user.png")

View File

@@ -0,0 +1,13 @@
add_library(test_extract_p1 SHARED test_extract.cpp)
add_library(test_extract_p2 SHARED test_extract.cpp)
add_library(test_source SHARED test_source.cpp)
add_library(test_incompat_api SHARED incompat_api.cpp)
target_include_directories(
test_extract_p1 PUBLIC "${LIBSCAP_INCLUDE_DIRS}")
target_include_directories(
test_extract_p2 PUBLIC "${LIBSCAP_INCLUDE_DIRS}")
target_include_directories(
test_source PUBLIC "${LIBSCAP_INCLUDE_DIRS}")

View File

@@ -0,0 +1,28 @@
/*
Copyright (C) 2021 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <string.h>
#include <stdlib.h>
// Don't need any function other than plugin_get_required_api_version,
// plugin load will fail after that.
static const char *pl_required_api_version = "10000000.0.0";
extern "C"
const char* plugin_get_required_api_version()
{
return pl_required_api_version;
}

View File

@@ -0,0 +1,116 @@
/*
Copyright (C) 2021 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <plugin_info.h>
static const char *pl_required_api_version = "0.1.0";
static uint32_t pl_type = TYPE_EXTRACTOR_PLUGIN;
static const char *pl_name_base = "test_extract";
static char pl_name[1024];
static const char *pl_desc = "Test Plugin For Regression Tests";
static const char *pl_contact = "github.com/falcosecurity/falco";
static const char *pl_version = "0.1.0";
static const char *pl_extract_sources = "[\"test_source\"]";
static const char *pl_fields = "[]";
// This struct represents the state of a plugin. Just has a placeholder string value.
typedef struct plugin_state
{
} plugin_state;
extern "C"
const char* plugin_get_required_api_version()
{
return pl_required_api_version;
}
extern "C"
uint32_t plugin_get_type()
{
return pl_type;
}
extern "C"
const char* plugin_get_name()
{
// Add a random-ish suffix to the end, as some tests load
// multiple copies of this plugin
snprintf(pl_name, sizeof(pl_name)-1, "%s%ld\n", pl_name_base, random());
return pl_name;
}
extern "C"
const char* plugin_get_description()
{
return pl_desc;
}
extern "C"
const char* plugin_get_contact()
{
return pl_contact;
}
extern "C"
const char* plugin_get_version()
{
return pl_version;
}
extern "C"
const char* plugin_get_extract_event_sources()
{
return pl_extract_sources;
}
extern "C"
const char* plugin_get_fields()
{
return pl_fields;
}
extern "C"
const char* plugin_get_last_error(ss_plugin_t* s)
{
return NULL;
}
extern "C"
ss_plugin_t* plugin_init(const char* config, int32_t* rc)
{
// Note: Using new/delete is okay, as long as the plugin
// framework is not deleting the memory.
plugin_state *ret = new plugin_state();
*rc = SS_PLUGIN_SUCCESS;
return ret;
}
extern "C"
void plugin_destroy(ss_plugin_t* s)
{
plugin_state *ps = (plugin_state *) s;
delete(ps);
}
extern "C"
int32_t plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event *evt, uint32_t num_fields, ss_plugin_extract_field *fields)
{
return SS_PLUGIN_SUCCESS;
}

View File

@@ -0,0 +1,161 @@
/*
Copyright (C) 2021 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <plugin_info.h>
static const char *pl_required_api_version = "0.1.0";
static uint32_t pl_type = TYPE_SOURCE_PLUGIN;
static uint32_t pl_id = 999;
static const char *pl_name = "test_source";
static const char *pl_desc = "Test Plugin For Regression Tests";
static const char *pl_contact = "github.com/falcosecurity/falco";
static const char *pl_version = "0.1.0";
static const char *pl_event_source = "test_source";
static const char *pl_fields = "[]";
// This struct represents the state of a plugin. Just has a placeholder string value.
typedef struct plugin_state
{
} plugin_state;
typedef struct instance_state
{
} instance_state;
extern "C"
const char* plugin_get_required_api_version()
{
return pl_required_api_version;
}
extern "C"
uint32_t plugin_get_type()
{
return pl_type;
}
extern "C"
uint32_t plugin_get_id()
{
return pl_id;
}
extern "C"
const char* plugin_get_name()
{
return pl_name;
}
extern "C"
const char* plugin_get_description()
{
return pl_desc;
}
extern "C"
const char* plugin_get_contact()
{
return pl_contact;
}
extern "C"
const char* plugin_get_version()
{
return pl_version;
}
extern "C"
const char* plugin_get_event_source()
{
return pl_event_source;
}
extern "C"
const char* plugin_get_fields()
{
return pl_fields;
}
extern "C"
const char* plugin_get_last_error(ss_plugin_t* s)
{
return NULL;
}
extern "C"
ss_plugin_t* plugin_init(const char* config, int32_t* rc)
{
// Note: Using new/delete is okay, as long as the plugin
// framework is not deleting the memory.
plugin_state *ret = new plugin_state();
*rc = SS_PLUGIN_SUCCESS;
return ret;
}
extern "C"
void plugin_destroy(ss_plugin_t* s)
{
plugin_state *ps = (plugin_state *) s;
delete(ps);
}
extern "C"
ss_instance_t* plugin_open(ss_plugin_t* s, const char* params, int32_t* rc)
{
// Note: Using new/delete is okay, as long as the plugin
// framework is not deleting the memory.
instance_state *ret = new instance_state();
*rc = SS_PLUGIN_SUCCESS;
return ret;
}
extern "C"
void plugin_close(ss_plugin_t* s, ss_instance_t* i)
{
instance_state *istate = (instance_state *) i;
delete(istate);
}
extern "C"
int32_t plugin_next_batch(ss_plugin_t* s, ss_instance_t* i, uint32_t *nevts, ss_plugin_event **evts)
{
return SS_PLUGIN_EOF;
}
// This plugin does not implement plugin_next_batch, due to the lower
// overhead of calling C functions from the plugin framework compared
// to calling Go functions.
extern "C"
const char *plugin_event_to_string(ss_plugin_t *s, const uint8_t *data, uint32_t datalen)
{
return "";
}
extern "C"
int32_t plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event *evt, uint32_t num_fields, ss_plugin_extract_field *fields)
{
return SS_PLUGIN_SUCCESS;
}

View File

@@ -0,0 +1,24 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- rule: open_from_cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen"
priority: WARNING
- rule: open_from_cat
enabled: false

View File

@@ -0,0 +1,25 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- rule: open_from_cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen"
priority: WARNING
enabled: false
- rule: open_from_cat
enabled: true

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env bash
#
# Copyright (C) 2019 The Falco Authors.
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +15,17 @@
# limitations under the License.
#
SUBJ_PID=$1
BENCHMARK=$2
VARIANT=$3
RESULTS_FILE=$4
CPU_INTERVAL=$5
- rule: Open From Cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen (command=%proc.cmdline)"
priority: WARNING
- rule: Open From Cat
exceptions:
- name: proc_cmdline
comps: in
values:
- "cat /dev/null"
append: true
top -d $CPU_INTERVAL -b -p $SUBJ_PID | grep -E '(falco|sysdig|dragent|test_mm)' --line-buffered | awk -v benchmark=$BENCHMARK -v variant=$VARIANT '{printf("{\"time\": \"%s\", \"sample\": %d, \"benchmark\": \"%s\", \"variant\": \"%s\", \"cpu_usage\": %s},\n", strftime("%Y-%m-%d %H:%M:%S", systime(), 1), NR, benchmark, variant, $9); fflush();}' >> $RESULTS_FILE

View File

@@ -0,0 +1,39 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- rule: Open From Cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen (command=%proc.cmdline)"
exceptions:
- name: proc_cmdline
fields: proc.cmdline
comps: in
values:
- cat /dev/zero
priority: WARNING
- rule: Open From Cat
exceptions:
- name: proc_cmdline_2
fields: proc.cmdline
comps: in
values:
- "cat /dev/null"
append: true

View File

@@ -0,0 +1,33 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- rule: Open From Cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen (command=%proc.cmdline)"
priority: WARNING
- rule: Open From Cat
exceptions:
- name: proc_cmdline
fields: proc.cmdline
comps: in
values:
- "cat /dev/null"
append: true

View File

@@ -1,2 +1,24 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- rule: no condition rule
desc: simpe rule
condition: evt.type=open
output: simple output
priority: WARNING
- rule: no condition rule
append: true

View File

@@ -0,0 +1,23 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- rule: json_pointer_example
desc: A rule example using JSON Pointer (RFC 6901)
condition: jevt.value[/annotations/authorization.k8s.io~1decision] exists and not (jevt.value[/a~1~0b] exists)
output: "JSON Pointer Test Alert"
priority: WARNING
source: k8s_audit

View File

@@ -0,0 +1,6 @@
- rule: Cloudtrail Create Instance
desc: Detect Creating an EC2 Instance
condition: evt.num > 0 and ct.name="StartInstances"
output: EC2 Instance Created (evtnum=%evt.num info=%evt.plugininfo id=%ct.id user name=%json.value[/userIdentity/userName])
priority: INFO
source: aws_cloudtrail

View File

@@ -0,0 +1,10 @@
- required_plugin_versions:
- name: cloudtrail
version: 100000.0.0
- rule: Cloudtrail Create Instance
desc: Detect Creating an EC2 Instance
condition: evt.num > 0 and ct.name="StartInstances"
output: EC2 Instance Created (evtnum=%evt.num info=%evt.plugininfo id=%ct.id user name=%json.value[/userIdentity/userName])
priority: INFO
source: aws_cloudtrail

View File

@@ -16,10 +16,10 @@
#
- rule: my_rule
desc: A process named cat does an open
condition: evt.type=open and fd.name=not-a-real-file
condition: (evt.type=open and fd.name=not-a-real-file)
output: "An open of /dev/null was seen (command=%proc.cmdline)"
priority: WARNING
- rule: my_rule
append: true
condition: or fd.name=/dev/null
condition: or (evt.type=open and fd.name=/dev/null)

View File

@@ -0,0 +1,34 @@
#
# Copyright (C) 2021 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
- required_engine_version: 2
- list: cat_binaries
items: [cat]
- list: cat_capable_binaries
items: [cat_binaries]
- macro: is_cat
condition: proc.name in (cat_capable_binaries)
- 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
tags: [filesystem, process, testing]

View File

@@ -15,7 +15,7 @@
# limitations under the License.
#
- macro: open_read
condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='f'
condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='f'
- rule: open_1
desc: open one

View File

@@ -1,444 +0,0 @@
#!/usr/bin/env bash
#
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#set -x
trap "cleanup; exit" SIGHUP SIGINT SIGTERM
TRACE_FILES_BASE_URL=${TRACE_FILES_BASE_URL:-"https://download.falco.org/fixtures/trace-files/"}
function download_trace_files() {
(mkdir -p $TRACEDIR && rm -rf $TRACEDIR/traces-perf && curl -fo $TRACEDIR/traces-perf.zip "${TRACE_FILES_BASE_URL}traces-perf.zip" && unzip -d $TRACEDIR $TRACEDIR/traces-perf.zip && rm -f $TRACEDIR/traces-perf.zip) || exit 1
}
function time_cmd() {
cmd="$1"
file="$2"
benchmark=`basename $file .scap`
echo -n "$benchmark: "
for i in `seq 1 5`; do
echo -n "$i "
time=`date --iso-8601=sec`
/usr/bin/time -a -o $RESULTS_FILE --format "{\"time\": \"$time\", \"benchmark\": \"$benchmark\", \"file\": \"$file\", \"variant\": \"$VARIANT\", \"elapsed\": {\"real\": %e, \"user\": %U, \"sys\": %S}}," $cmd >> $OUTPUT_FILE 2>&1
done
echo ""
}
function run_falco_on() {
file="$1"
if [ -z $RULES_FILE ]; then
RULES_FILE=$SOURCE/rules/falco_rules.yaml
fi
cmd="$ROOT/userspace/falco/falco -c $SOURCE/falco.yaml -r $SOURCE/rules/falco_rules.yaml --option=stdout_output.enabled=false -e $file -A"
time_cmd "$cmd" "$file"
}
function run_sysdig_on() {
file="$1"
cmd="$ROOT/userspace/sysdig/sysdig -N -z -r $file evt.type=none"
time_cmd "$cmd" "$file"
}
function write_agent_config() {
cat > $ROOT/userspace/dragent/dragent.yaml <<EOF
customerid: XXX
app_checks_enabled: false
log:
file_priority: info
console_priority: info
event_priority: info
jmx:
enabled: false
statsd:
enabled: false
collector: collector-staging.sysdigcloud.com
EOF
if [ $FALCO_AGENT == 1 ]; then
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
falco_engine:
enabled: true
rules_filename: /etc/falco_rules.yaml
sampling_multiplier: 0
EOF
else
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
falco_engine:
enabled: false
EOF
fi
if [ $AGENT_AUTODROP == 1 ]; then
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
autodrop:
enabled: true
EOF
else
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
autodrop:
enabled: false
EOF
fi
cat $ROOT/userspace/dragent/dragent.yaml
}
function run_agent_on() {
file="$1"
write_agent_config
cmd="$ROOT/userspace/dragent/dragent -r $file"
time_cmd "$cmd" "$file"
}
function run_trace() {
if [ ! -e $TRACEDIR ]; then
download_trace_files
fi
trace_file="$1"
if [ $trace_file == "all" ]; then
files=($TRACEDIR/traces-perf/*.scap)
else
files=($TRACEDIR/traces-perf/$trace_file.scap)
fi
for file in "${files[@]}"; do
if [[ $ROOT == *"falco"* ]]; then
run_falco_on "$file"
elif [[ $ROOT == *"sysdig"* ]]; then
run_sysdig_on "$file"
else
run_agent_on "$file"
fi
done
}
function start_monitor_cpu_usage() {
echo " monitoring cpu usage for falcosecurity/falco program"
setsid bash `dirname $0`/cpu_monitor.sh $SUBJ_PID $live_test $VARIANT $RESULTS_FILE $CPU_INTERVAL &
CPU_PID=$!
sleep 5
}
function start_subject_prog() {
# Do a blocking sudo command now just to ensure we have a password
sudo bash -c ""
if [[ $ROOT == *"multimatch"* ]]; then
echo " starting test_mm..."
if [ -z $RULES_FILE ]; then
RULES_FILE=$SOURCE/../output/rules.yaml
fi
sudo FALCO_STATS_EXTRA_variant=$VARIANT FALCO_STATS_EXTRA_benchmark=$live_test $ROOT/test_mm -S $SOURCE/search_order.yaml -s $STATS_FILE -r $RULES_FILE > ./prog-output.txt 2>&1 &
elif [[ $ROOT == *"falco"* ]]; then
echo " starting falco..."
if [ -z $RULES_FILE ]; then
RULES_FILE=$SOURCE/rules/falco_rules.yaml
fi
sudo FALCO_STATS_EXTRA_variant=$VARIANT FALCO_STATS_EXTRA_benchmark=$live_test $ROOT/userspace/falco/falco -c $SOURCE/falco.yaml -s $STATS_FILE -r $RULES_FILE --option=stdout_output.enabled=false > ./prog-output.txt -A 2>&1 &
elif [[ $ROOT == *"sysdig"* ]]; then
echo " starting sysdig..."
sudo $ROOT/userspace/sysdig/sysdig -N -z evt.type=none &
else
echo " starting agent..."
write_agent_config
pushd $ROOT/userspace/dragent
sudo ./dragent > ./prog-output.txt 2>&1 &
popd
fi
SUDO_PID=$!
sleep 5
if [[ $ROOT == *"agent"* ]]; then
# The agent spawns several processes all below a main monitor
# process. We want the child with the lowest pid.
MON_PID=`ps -h -o pid --ppid $SUDO_PID`
SUBJ_PID=`ps -h -o pid --ppid $MON_PID | head -1`
else
SUBJ_PID=`ps -h -o pid --ppid $SUDO_PID`
fi
if [ -z $SUBJ_PID ]; then
echo "Could not find pid of subject program--did it start successfully? Not continuing."
exit 1
fi
}
function run_htop() {
screen -S htop-screen -d -m /usr/bin/htop -d2
sleep 90
screen -X -S htop-screen quit
}
function run_juttle_examples() {
pushd $SCRIPTDIR/../../juttle-engine/examples
docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml up -d
sleep 120
docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml stop
docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml rm -fv
popd
}
function run_kubernetes_demo() {
pushd $SCRIPTDIR/../../infrastructure/test-infrastructures/kubernetes-demo
sudo bash run-local.sh
sudo bash init.sh
sleep 600
docker stop $(docker ps -qa)
docker rm -fv $(docker ps -qa)
popd
}
function run_live_test() {
live_test="$1"
echo "Running live test $live_test"
case "$live_test" in
htop ) CPU_INTERVAL=2;;
* ) CPU_INTERVAL=10;;
esac
start_subject_prog
start_monitor_cpu_usage
echo " starting live program and waiting for it to finish"
case "$live_test" in
htop ) run_htop ;;
juttle-examples ) run_juttle_examples ;;
kube-demo ) run_kubernetes_demo ;;
* ) usage; cleanup; exit 1 ;;
esac
cleanup
}
function cleanup() {
if [ -n "$SUBJ_PID" ] ; then
echo " stopping falco/sysdig program $SUBJ_PID"
sudo kill $SUBJ_PID
fi
if [ -n "$CPU_PID" ] ; then
echo " stopping cpu monitor program $CPU_PID"
kill -- -$CPU_PID
fi
}
run_live_tests() {
test="$1"
if [ $test == "all" ]; then
tests="htop juttle-examples kube-demo"
else
tests=$test
fi
for test in $tests; do
run_live_test $test
done
}
function run_phoronix_test() {
live_test="$1"
case "$live_test" in
pts/aio-stress | pts/fs-mark | pts/iozone | pts/network-loopback | pts/nginx | pts/pybench | pts/redis | pts/sqlite | pts/unpack-linux ) CPU_INTERVAL=2;;
* ) CPU_INTERVAL=10;;
esac
echo "Running phoronix test $live_test"
start_subject_prog
start_monitor_cpu_usage
echo " starting phoronix test and waiting for it to finish"
TEST_RESULTS_NAME=$VARIANT FORCE_TIMES_TO_RUN=1 phoronix-test-suite default-run $live_test
cleanup
}
# To install and configure phoronix:
# (redhat instructions, adapt as necessary for ubuntu or other distros)
# - install phoronix: yum install phoronix-test-suite.noarch
# - install dependencies not handled by phoronix: yum install libaio-devel pcre-devel popt-devel glibc-static zlib-devel nc bc
# - fix trivial bugs in tests:
# - edit ~/.phoronix-test-suite/installed-tests/pts/network-loopback-1.0.1/network-loopback line "nc -d -l 9999 > /dev/null &" to "nc -d -l 9999 > /dev/null &"
# - edit ~/.phoronix-test-suite/test-profiles/pts/nginx-1.1.0/test-definition.xml line "<Arguments>-n 500000 -c 100 http://localhost:8088/test.html</Arguments>" to "<Arguments>-n 500000 -c 100 http://127.0.0.1:8088/test.html</Arguments>"
# - phoronix batch-install <test list below>
function run_phoronix_tests() {
test="$1"
if [ $test == "all" ]; then
tests="pts/aio-stress pts/apache pts/blogbench pts/compilebench pts/dbench pts/fio pts/fs-mark pts/iozone pts/network-loopback pts/nginx pts/pgbench pts/phpbench pts/postmark pts/pybench pts/redis pts/sqlite pts/unpack-linux"
else
tests=$test
fi
for test in $tests; do
run_phoronix_test $test
done
}
run_tests() {
IFS=':' read -ra PARTS <<< "$TEST"
case "${PARTS[0]}" in
trace ) run_trace "${PARTS[1]}" ;;
live ) run_live_tests "${PARTS[1]}" ;;
phoronix ) run_phoronix_tests "${PARTS[1]}" ;;
* ) usage; exit 1 ;;
esac
}
usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -h/--help: show this help"
echo " -v/--variant: a variant name to attach to this set of test results"
echo " -r/--root: root directory containing falco/sysdig binaries (i.e. where you ran 'cmake')"
echo " -s/--source: root directory containing falco/sysdig source code"
echo " -R/--results: append test results to this file"
echo " -S/--stats: append capture statistics to this file (only works for falco/test_mm)"
echo " -o/--output: append program output to this file"
echo " -U/--rules: path to rules file (only applicable for falco/test_mm)"
echo " -t/--test: test to run. Argument has the following format:"
echo " trace:<trace>: read the specified trace file."
echo " trace:all means run all traces"
echo " live:<live test>: run the specified live test."
echo " live:all means run all live tests."
echo " possible live tests:"
echo " live:htop: run htop -d2"
echo " live:kube-demo: run kubernetes demo from infrastructure repo"
echo " live:juttle-examples: run a juttle demo environment based on docker-compose"
echo " phoronix:<test>: run the specified phoronix test."
echo " if <test> is not 'all', it is passed directly to the command line of \"phoronix-test-suite run <test>\""
echo " if <test> is 'all', a built-in set of phoronix tests will be chosen and run"
echo " -T/--tracedir: Look for trace files in this directory. If doesn't exist, will download trace files from s3"
echo " -A/--agent-autodrop: When running an agent, whether or not to enable autodrop"
echo " -F/--falco-agent: When running an agent, whether or not to enable falco"
}
OPTS=`getopt -o hv:r:s:R:S:o:U:t:T: --long help,variant:,root:,source:,results:,stats:,output:,rules:,test:,tracedir:,agent-autodrop:,falco-agent: -n $0 -- "$@"`
if [ $? != 0 ]; then
echo "Exiting" >&2
exit 1
fi
eval set -- "$OPTS"
VARIANT="falco"
ROOT=`dirname $0`/../build
SOURCE=$ROOT
SCRIPTDIR=`dirname $0`
RESULTS_FILE=`dirname $0`/results.json
STATS_FILE=`dirname $0`/capture_stats.json
OUTPUT_FILE=`dirname $0`/program-output.txt
RULES_FILE=
TEST=trace:all
TRACEDIR=/tmp/falco-perf-traces.$USER
CPU_INTERVAL=10
AGENT_AUTODROP=1
FALCO_AGENT=1
while true; do
case "$1" in
-h | --help ) usage; exit 1;;
-v | --variant ) VARIANT="$2"; shift 2;;
-r | --root ) ROOT="$2"; shift 2;;
-s | --source ) SOURCE="$2"; shift 2;;
-R | --results ) RESULTS_FILE="$2"; shift 2;;
-S | --stats ) STATS_FILE="$2"; shift 2;;
-o | --output ) OUTPUT_FILE="$2"; shift 2;;
-U | --rules ) RULES_FILE="$2"; shift 2;;
-t | --test ) TEST="$2"; shift 2;;
-T | --tracedir ) TRACEDIR="$2"; shift 2;;
-A | --agent-autodrop ) AGENT_AUTODROP="$2"; shift 2;;
-F | --falco-agent ) FALCO_AGENT="$2"; shift 2;;
* ) break;;
esac
done
if [ -z $VARIANT ]; then
echo "A test variant name must be provided. Not continuing."
exit 1
fi
if [ -z $ROOT ]; then
echo "A root directory containing a falco/sysdig binary must be provided. Not continuing."
exit 1
fi
ROOT=`realpath $ROOT`
if [ -z $SOURCE ]; then
echo "A source directory containing falco/sysdig source code. Not continuing."
exit 1
fi
SOURCE=`realpath $SOURCE`
if [ -z $RESULTS_FILE ]; then
echo "An output file for test results must be provided. Not continuing."
exit 1
fi
if [ -z $STATS_FILE ]; then
echo "An output file for capture statistics must be provided. Not continuing."
exit 1
fi
if [ -z $OUTPUT_FILE ]; then
echo "An file for program output must be provided. Not continuing."
exit 1
fi
if [ -z $TEST ]; then
echo "A test must be provided. Not continuing."
exit 1
fi
run_tests

View File

@@ -20,6 +20,7 @@ set -euo pipefail
SCRIPT=$(readlink -f $0)
SCRIPTDIR=$(dirname "$SCRIPT")
SKIP_PACKAGES_TESTS=${SKIP_PACKAGES_TESTS:-false}
SKIP_PLUGINS_TESTS=${SKIP_PLUGINS_TESTS:-false}
TRACE_FILES_BASE_URL=${TRACE_FILES_BASE_URL:-"https://download.falco.org/fixtures/trace-files/"}
# Trace file tarballs are now versioned. Any time a substantial change
@@ -104,6 +105,10 @@ function run_tests() {
if [ "$SKIP_PACKAGES_TESTS" = false ] ; then
suites+=($SCRIPTDIR/falco_tests_package.yaml)
fi
if [ "$SKIP_PLUGINS_TESTS" = false ] ; then
suites+=($SCRIPTDIR/falco_tests_plugins.yaml)
fi
XUNIT_DIR="${OPT_BUILD_DIR}/integration-tests-xunit"
mkdir -p "${XUNIT_DIR}"

View File

@@ -1,5 +1,6 @@
add_subdirectory(k8s_audit)
add_subdirectory(psp)
add_subdirectory(plugins)
# Note: list of traces is created at cmake time, not build time
file(GLOB test_trace_files
@@ -16,4 +17,4 @@ foreach(trace_file_path ${test_trace_files})
endforeach()
add_custom_target(trace-files-base-scap ALL)
add_dependencies(trace-files-base-scap ${BASE_SCAP_TRACE_FILES_TARGETS})
add_dependencies(trace-files-base-scap ${BASE_SCAP_TRACE_FILES_TARGETS})

View File

@@ -0,0 +1,16 @@
# Note: list of traces is created at cmake time, not build time
file(GLOB test_trace_files
"${CMAKE_CURRENT_SOURCE_DIR}/*.json")
foreach(trace_file_path ${test_trace_files})
get_filename_component(trace_file ${trace_file_path} NAME)
add_custom_target(test-trace-${trace_file} ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${trace_file})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${trace_file}
COMMAND ${CMAKE_COMMAND} -E copy ${trace_file_path} ${CMAKE_CURRENT_BINARY_DIR}/${trace_file}
DEPENDS ${trace_file_path})
list(APPEND PLUGINS_TRACE_FILES_TARGETS test-trace-${trace_file})
endforeach()
add_custom_target(trace-files-plugins ALL)
add_dependencies(trace-files-plugins ${PLUGINS_TRACE_FILES_TARGETS})

View File

@@ -0,0 +1,31 @@
{"Records": [{
"eventVersion": "1.0",
"userIdentity": {
"type": "IAMUser",
"principalId": "EX_PRINCIPAL_ID",
"arn": "arn:aws:iam::123456789012:user/Alice",
"accessKeyId": "EXAMPLE_KEY_ID",
"accountId": "123456789012",
"userName": "Alice"
},
"eventTime": "2014-03-06T21:22:54Z",
"eventSource": "ec2.amazonaws.com",
"eventType": "AwsApiCall",
"eventName": "StartInstances",
"awsRegion": "us-east-2",
"sourceIPAddress": "205.251.233.176",
"userAgent": "ec2-api-tools 1.6.12.2",
"requestParameters": {"instancesSet": {"items": [{"instanceId": "i-ebeaf9e2"}]}},
"responseElements": {"instancesSet": {"items": [{
"instanceId": "i-ebeaf9e2",
"currentState": {
"code": 0,
"name": "pending"
},
"previousState": {
"code": 80,
"name": "stopped"
}
}]}}
}]}

File diff suppressed because one or more lines are too long

View File

@@ -15,12 +15,25 @@
# the License.
#
if(MINIMAL_BUILD)
set(FALCO_TESTS_SOURCES test_base.cpp engine/test_token_bucket.cpp engine/test_rulesets.cpp engine/test_falco_utils.cpp)
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
falco/test_configuration.cpp
)
else()
set(FALCO_TESTS_SOURCES test_base.cpp engine/test_token_bucket.cpp engine/test_rulesets.cpp engine/test_falco_utils.cpp falco/test_webserver.cpp)
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
falco/test_configuration.cpp
falco/test_webserver.cpp
)
endif()
set(FALCO_TESTED_LIBRARIES falco_engine)
set(FALCO_TESTED_LIBRARIES falco_engine ${YAMLCPP_LIB})
SET(FALCO_TESTS_ARGUMENTS "" CACHE STRING "Test arguments to pass to the Falco test suite")
@@ -46,6 +59,7 @@ if(FALCO_BUILD_TESTS)
PUBLIC "${CATCH2_INCLUDE}"
"${FAKEIT_INCLUDE}"
"${PROJECT_SOURCE_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/falco"
"${YAMLCPP_INCLUDE_DIR}"
"${PROJECT_SOURCE_DIR}/userspace/falco")
else()
@@ -54,6 +68,7 @@ if(FALCO_BUILD_TESTS)
PUBLIC "${CATCH2_INCLUDE}"
"${FAKEIT_INCLUDE}"
"${PROJECT_SOURCE_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/falco"
"${YAMLCPP_INCLUDE_DIR}"
"${CIVETWEB_INCLUDE_DIR}"
"${PROJECT_SOURCE_DIR}/userspace/falco")

View File

@@ -25,15 +25,25 @@ static uint16_t default_ruleset = 0;
static uint16_t non_default_ruleset = 3;
static uint16_t other_non_default_ruleset = 2;
static std::set<std::string> tags = {"some_tag", "some_other_tag"};
static std::set<uint32_t> event_tags = {1};
static std::shared_ptr<gen_event_filter> create_filter()
{
// The actual contents of the filters don't matter here.
sinsp_filter_compiler compiler(NULL, "evt.type=open");
sinsp_filter *f = compiler.compile();
std::shared_ptr<gen_event_filter> ret(f);
return ret;
}
TEST_CASE("Should enable/disable for exact match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("one_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -45,10 +55,10 @@ TEST_CASE("Should enable/disable for exact match w/ default ruleset", "[rulesets
TEST_CASE("Should enable/disable for exact match w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("one_rule", exact_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
@@ -64,10 +74,10 @@ TEST_CASE("Should enable/disable for exact match w/ specific ruleset", "[ruleset
TEST_CASE("Should not enable for exact match different rule name", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("some_other_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
@@ -76,10 +86,10 @@ TEST_CASE("Should not enable for exact match different rule name", "[rulesets]")
TEST_CASE("Should enable/disable for exact match w/ substring and default ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("one_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -91,10 +101,10 @@ TEST_CASE("Should enable/disable for exact match w/ substring and default rulese
TEST_CASE("Should not enable for substring w/ exact_match", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("one_", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
@@ -103,10 +113,10 @@ TEST_CASE("Should not enable for substring w/ exact_match", "[rulesets]")
TEST_CASE("Should enable/disable for prefix match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("one_", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -118,10 +128,10 @@ TEST_CASE("Should enable/disable for prefix match w/ default ruleset", "[ruleset
TEST_CASE("Should enable/disable for suffix match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -133,10 +143,10 @@ TEST_CASE("Should enable/disable for suffix match w/ default ruleset", "[ruleset
TEST_CASE("Should enable/disable for substring match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("ne_ru", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -148,10 +158,10 @@ TEST_CASE("Should enable/disable for substring match w/ default ruleset", "[rule
TEST_CASE("Should enable/disable for substring match w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable("ne_ru", substring_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
@@ -167,11 +177,11 @@ TEST_CASE("Should enable/disable for substring match w/ specific ruleset", "[rul
TEST_CASE("Should enable/disable for tags w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag"};
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -183,11 +193,11 @@ TEST_CASE("Should enable/disable for tags w/ default ruleset", "[rulesets]")
TEST_CASE("Should enable/disable for tags w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag"};
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
@@ -203,11 +213,11 @@ TEST_CASE("Should enable/disable for tags w/ specific ruleset", "[rulesets]")
TEST_CASE("Should not enable for different tags", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_different_tag"};
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
@@ -216,11 +226,11 @@ TEST_CASE("Should not enable for different tags", "[rulesets]")
TEST_CASE("Should enable/disable for overlapping tags", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag", "some_different_tag"};
r.add(rule_name, tags, event_tags, filter);
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -232,15 +242,15 @@ TEST_CASE("Should enable/disable for overlapping tags", "[rulesets]")
TEST_CASE("Should enable/disable for incremental adding tags", "[rulesets]")
{
falco_ruleset r;
gen_event_filter *rule1_filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> rule1_filter = create_filter();
string rule1_name = "one_rule";
std::set<std::string> rule1_tags = {"rule1_tag"};
r.add(rule1_name, rule1_tags, event_tags, rule1_filter);
r.add(rule1_name, rule1_tags, rule1_filter);
gen_event_filter *rule2_filter = new gen_event_filter();
std::shared_ptr<gen_event_filter> rule2_filter = create_filter();
string rule2_name = "two_rule";
std::set<std::string> rule2_tags = {"rule2_tag"};
r.add(rule2_name, rule2_tags, event_tags, rule2_filter);
r.add(rule2_name, rule2_tags, rule2_filter);
std::set<std::string> want_tags;

View File

@@ -1,83 +0,0 @@
/*
Copyright (C) 2019 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <memory>
#include "token_bucket.h"
#include <catch.hpp>
using namespace Catch::literals;
TEST_CASE("token bucket default ctor", "[token_bucket]")
{
auto tb = std::make_shared<token_bucket>();
REQUIRE(tb->get_tokens() == 1);
SECTION("initialising with specific time, rate 2 tokens/sec")
{
auto max = 2.0;
uint64_t now = 1;
tb->init(1.0, max, now);
REQUIRE(tb->get_last_seen() == now);
REQUIRE(tb->get_tokens() == max);
}
}
TEST_CASE("token bucket ctor with custom timer", "[token_bucket]")
{
auto t = []() -> uint64_t { return 22; };
auto tb = std::make_shared<token_bucket>(t);
REQUIRE(tb->get_tokens() == 1);
REQUIRE(tb->get_last_seen() == 22);
}
TEST_CASE("token bucket with 2 tokens/sec rate, max 10 tokens", "[token_bucket]")
{
auto tb = std::make_shared<token_bucket>();
tb->init(2.0, 10, 1);
SECTION("claiming 5 tokens")
{
bool claimed = tb->claim(5, 1000000001);
REQUIRE(tb->get_last_seen() == 1000000001);
REQUIRE(tb->get_tokens() == 5.0_a);
REQUIRE(claimed);
SECTION("claiming all the 7 remaining tokens")
{
bool claimed = tb->claim(7, 2000000001);
REQUIRE(tb->get_last_seen() == 2000000001);
REQUIRE(tb->get_tokens() == 0.0_a);
REQUIRE(claimed);
SECTION("claiming 1 token more than the 2 available fails")
{
bool claimed = tb->claim(3, 3000000001);
REQUIRE(tb->get_last_seen() == 3000000001);
REQUIRE(tb->get_tokens() == 2.0_a);
REQUIRE_FALSE(claimed);
}
}
}
}
TEST_CASE("token bucket default initialization", "[token_bucket]")
{
token_bucket tb;
REQUIRE(tb.get_tokens() == 1);
}

View File

@@ -0,0 +1,106 @@
/*
Copyright (C) 2021 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "configuration.h"
#include <catch.hpp>
string sample_yaml =
"base_value:\n"
" id: 1\n"
" name: 'sample_name'\n"
" subvalue:\n"
" subvalue2:\n"
" boolean: true\n"
"base_value_2:\n"
" sample_list:\n"
" - elem1\n"
" - elem2\n"
" - elem3\n"
;
TEST_CASE("configuration must load YAML data", "[configuration]")
{
yaml_configuration conf;
SECTION("broken YAML")
{
string sample_broken_yaml = sample_yaml + " / bad_symbol";
REQUIRE_THROWS(conf.load_from_string(sample_broken_yaml));
}
SECTION("valid YAML")
{
REQUIRE_NOTHROW(conf.load_from_string(sample_yaml));
}
SECTION("clearing and reloading")
{
conf.load_from_string(sample_yaml);
REQUIRE(conf.is_defined("base_value") == true);
conf.clear();
REQUIRE(conf.is_defined("base_value") == false);
conf.load_from_string(sample_yaml);
REQUIRE(conf.is_defined("base_value") == true);
}
}
TEST_CASE("configuration must read YAML fields", "[configuration]")
{
yaml_configuration conf;
conf.load_from_string(sample_yaml);
SECTION("base level")
{
REQUIRE(conf.is_defined("base_value") == true);
REQUIRE(conf.is_defined("base_value_2") == true);
REQUIRE(conf.is_defined("unknown_base_value") == false);
}
SECTION("arbitrary depth nesting")
{
REQUIRE(conf.get_scalar<int>("base_value.id", -1) == 1);
REQUIRE(conf.get_scalar<string>("base_value.name", "none") == "sample_name");
REQUIRE(conf.get_scalar<bool>("base_value.subvalue.subvalue2.boolean", false) == true);
}
SECTION("list field elements")
{
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[0]", "none") == "elem1");
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[1]", "none") == "elem2");
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[2]", "none") == "elem3");
}
SECTION("sequence")
{
vector<string> seq;
conf.get_sequence(seq, "base_value_2.sample_list");
REQUIRE(seq.size() == 3);
REQUIRE(seq[0] == "elem1");
REQUIRE(seq[1] == "elem2");
REQUIRE(seq[2] == "elem3");
}
}
TEST_CASE("configuration must modify YAML fields", "[configuration]")
{
string key = "base_value.subvalue.subvalue2.boolean";
yaml_configuration conf;
conf.load_from_string(sample_yaml);
REQUIRE(conf.get_scalar<bool>(key, false) == true);
conf.set_scalar<bool>(key, false);
REQUIRE(conf.get_scalar<bool>(key, true) == false);
conf.set_scalar<bool>(key, true);
REQUIRE(conf.get_scalar<bool>(key, false) == true);
}

View File

@@ -10,6 +10,8 @@
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
add_subdirectory(lua)
set(FALCO_ENGINE_SOURCE_FILES
rules.cpp
falco_common.cpp
@@ -17,7 +19,6 @@ set(FALCO_ENGINE_SOURCE_FILES
falco_utils.cpp
json_evt.cpp
ruleset.cpp
token_bucket.cpp
formats.cpp)
add_library(falco_engine STATIC ${FALCO_ENGINE_SOURCE_FILES})
@@ -37,7 +38,8 @@ if(MINIMAL_BUILD)
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/userspace/engine")
"${PROJECT_BINARY_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/engine/lua")
else()
target_include_directories(
falco_engine
@@ -49,24 +51,11 @@ else()
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/userspace/engine")
"${PROJECT_BINARY_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/engine/lua")
endif()
target_link_libraries(falco_engine "${FALCO_SINSP_LIBRARY}" "${LPEG_LIB}" "${LYAML_LIB}" "${LIBYAML_LIB}")
target_link_libraries(falco_engine "${FALCO_SINSP_LIBRARY}" "${LPEG_LIB}" "${LYAML_LIB}" "${LIBYAML_LIB}" luafiles)
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()

View File

@@ -19,6 +19,7 @@ limitations under the License.
#include "config_falco_engine.h"
#include "falco_common.h"
#include "banned.h" // This raises a compilation error when certain functions are used
#include "falco_engine_lua_files.hh"
std::vector<std::string> falco_common::priority_names = {
"Emergency",
@@ -48,73 +49,33 @@ falco_common::~falco_common()
}
}
void falco_common::set_inspector(sinsp *inspector)
void falco_common::init()
{
m_inspector = inspector;
}
void falco_common::init(const char *lua_main_filename, const char *alternate_lua_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())
// Strings in the list lua_module_strings need to be loaded as
// lua modules, which also involves adding them to the
// package.module table.
for(const auto &pair : lua_module_strings)
{
lua_dir = alternate_lua_dir;
lua_main_path = lua_dir + lua_main_filename;
lua_getglobal(m_ls, "package");
lua_getfield(m_ls, -1, "preload");
is.open(lua_main_path);
if (!is.is_open())
if(luaL_loadstring(m_ls, pair.first))
{
throw falco_exception("Could not find Falco Lua entrypoint (tried " +
string(FALCO_ENGINE_LUA_DIR) + lua_main_filename + ", " +
string(alternate_lua_dir) + lua_main_filename + ")");
throw falco_exception("Failed to load embedded lua code " +
string(pair.second) + ": " + lua_tostring(m_ls, -1));
}
lua_setfield(m_ls, -2, pair.second);
}
// Strings in the list lua_code_strings need to be loaded and
// evaluated so any public functions can be directly called.
for(const auto &str : lua_code_strings)
{
if(luaL_loadstring(m_ls, str) || lua_pcall(m_ls, 0, 0, 0))
{
throw falco_exception("Failed to load + evaluate embedded lua code " +
string(str) + ": " + lua_tostring(m_ls, -1));
}
}
// 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

@@ -69,9 +69,7 @@ public:
falco_common();
virtual ~falco_common();
void init(const char *lua_main_filename, const char *alternate_lua_dir);
void set_inspector(sinsp *inspector);
void init();
// Priority levels, as a vector of strings
static std::vector<std::string> priority_names;
@@ -93,9 +91,4 @@ protected:
lua_State *m_ls;
std::mutex m_ls_semaphore;
sinsp *m_inspector;
private:
void add_lua_path(std::string &path);
};
};

View File

@@ -19,6 +19,9 @@ limitations under the License.
#include <string>
#include <fstream>
#include <sinsp.h>
#include <plugin.h>
#include "falco_engine.h"
#include "falco_utils.h"
#include "falco_engine_version.h"
@@ -37,13 +40,12 @@ extern "C" {
string lua_on_event = "on_event";
string lua_print_stats = "print_stats";
const std::string falco_engine::s_default_ruleset = "falco-default-ruleset";
using namespace std;
nlohmann::json::json_pointer falco_engine::k8s_audit_time = "/stageTimestamp"_json_pointer;
falco_engine::falco_engine(bool seed_rng, const std::string& alternate_lua_dir)
: m_rules(NULL), m_next_ruleset_id(0),
: m_next_ruleset_id(0),
m_min_priority(falco_common::PRIORITY_DEBUG),
m_sampling_ratio(1), m_sampling_multiplier(0),
m_replace_container_info(false)
@@ -51,29 +53,21 @@ falco_engine::falco_engine(bool seed_rng, const std::string& alternate_lua_dir)
luaopen_lpeg(m_ls);
luaopen_yaml(m_ls);
falco_common::init(m_lua_main_filename.c_str(), alternate_lua_dir.c_str());
falco_common::init();
falco_rules::init(m_ls);
m_sinsp_rules.reset(new falco_sinsp_ruleset());
m_k8s_audit_rules.reset(new falco_ruleset());
m_required_plugin_versions.clear();
if(seed_rng)
{
srandom((unsigned) getpid());
}
m_default_ruleset_id = find_ruleset_id(m_default_ruleset);
// Create this now so we can potentially list filters and exit
m_json_factory = make_shared<json_event_filter_factory>();
m_default_ruleset_id = find_ruleset_id(s_default_ruleset);
}
falco_engine::~falco_engine()
{
if (m_rules)
{
delete m_rules;
}
}
uint32_t falco_engine::engine_version()
@@ -81,65 +75,70 @@ uint32_t falco_engine::engine_version()
return (uint32_t) FALCO_ENGINE_VERSION;
}
#define DESCRIPTION_TEXT_START 16
#define CONSOLE_LINE_LEN 79
void falco_engine::list_fields(bool names_only)
// Return a key that uniquely represents a field class.
// For now, we assume name + shortdesc is unique.
static std::string fieldclass_key(const gen_event_filter_factory::filter_fieldclass_info &fld_info)
{
for(auto &chk_field : json_factory().get_fields())
return fld_info.name + fld_info.shortdesc;
}
void falco_engine::list_fields(std::string &source, bool verbose, bool names_only)
{
// Maps from field class name + short desc to list of event
// sources for which this field class can be used.
std::map<std::string,std::set<std::string>> fieldclass_event_sources;
// Do a first pass to group together classes that are
// applicable to multiple event sources.
for(auto &it : m_filter_factories)
{
if(!names_only)
if(source != "" && source != it.first)
{
printf("\n----------------------\n");
printf("Field Class: %s (%s)\n\n", chk_field.m_name.c_str(), chk_field.m_desc.c_str());
if(chk_field.m_class_info != "")
{
std::string str = falco::utils::wrap_text(chk_field.m_class_info, 0, 0, CONSOLE_LINE_LEN);
printf("%s\n", str.c_str());
}
continue;
}
for(auto &field : chk_field.m_fields)
for(auto &fld_class : it.second->get_fields())
{
printf("%s", field.m_name.c_str());
fieldclass_event_sources[fieldclass_key(fld_class)].insert(it.first);
}
}
if(names_only)
// The set of field classes already printed. Used to avoid
// printing field classes multiple times for different sources
std::set<std::string> seen_fieldclasses;
// In the second pass, actually print info, skipping duplicate
// field classes and also printing info on supported sources.
for(auto &it : m_filter_factories)
{
if(source != "" && source != it.first)
{
continue;
}
for(auto &fld_class : it.second->get_fields())
{
std::string key = fieldclass_key(fld_class);
if(seen_fieldclasses.find(key) != seen_fieldclasses.end())
{
printf("\n");
continue;
}
uint32_t namelen = field.m_name.size();
if(namelen >= DESCRIPTION_TEXT_START)
seen_fieldclasses.insert(key);
if(!names_only)
{
printf("\n");
namelen = 0;
printf("%s\n", fld_class.as_string(verbose,
fieldclass_event_sources[fieldclass_key(fld_class)]).c_str());
}
for(uint32_t l = 0; l < DESCRIPTION_TEXT_START - namelen; l++)
else
{
printf(" ");
for(auto &field : fld_class.fields)
{
printf("%s\n", field.name.c_str());
}
}
std::string desc = field.m_desc;
switch(field.m_idx_mode)
{
case json_event_filter_check::IDX_REQUIRED:
case json_event_filter_check::IDX_ALLOWED:
desc += " (";
desc += json_event_filter_check::s_index_mode_strs[field.m_idx_mode];
desc += ", ";
desc += json_event_filter_check::s_index_type_strs[field.m_idx_type];
desc += ")";
break;
case json_event_filter_check::IDX_NONE:
default:
break;
};
std::string str = falco::utils::wrap_text(desc, namelen, DESCRIPTION_TEXT_START, CONSOLE_LINE_LEN);
printf("%s\n", str.c_str());
}
}
}
@@ -153,33 +152,18 @@ void falco_engine::load_rules(const string &rules_content, bool verbose, bool al
void falco_engine::load_rules(const string &rules_content, bool verbose, bool all_events, uint64_t &required_engine_version)
{
// The engine must have been given an inspector by now.
if(! m_inspector)
{
throw falco_exception("No inspector provided");
}
if(!m_sinsp_factory)
{
m_sinsp_factory = make_shared<sinsp_filter_factory>(m_inspector);
}
if(!m_rules)
{
m_rules = new falco_rules(m_inspector,
this,
m_ls);
m_rules.reset(new falco_rules(this,
m_ls));
for(auto const &it : m_filter_factories)
{
m_rules->add_filter_factory(it.first, it.second);
}
}
// Note that falco_formats is added to the lua state used
// by the falco engine only. Within the engine, only
// formats.formatter is used, so we can unconditionally set
// json_output to false.
bool json_output = false;
bool json_include_output_property = false;
falco_formats::init(m_inspector, this, m_ls, json_output, json_include_output_property);
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority, required_engine_version);
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority, required_engine_version, m_required_plugin_versions);
}
void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events)
@@ -212,13 +196,10 @@ void falco_engine::enable_rule(const string &substring, bool enabled, const stri
uint16_t ruleset_id = find_ruleset_id(ruleset);
bool match_exact = false;
m_sinsp_rules->enable(substring, match_exact, enabled, ruleset_id);
m_k8s_audit_rules->enable(substring, match_exact, enabled, ruleset_id);
}
void falco_engine::enable_rule(const string &substring, bool enabled)
{
enable_rule(substring, enabled, m_default_ruleset);
for(auto &it : m_rulesets)
{
it.second->enable(substring, match_exact, enabled, ruleset_id);
}
}
void falco_engine::enable_rule_exact(const string &rule_name, bool enabled, const string &ruleset)
@@ -226,26 +207,20 @@ void falco_engine::enable_rule_exact(const string &rule_name, bool enabled, cons
uint16_t ruleset_id = find_ruleset_id(ruleset);
bool match_exact = true;
m_sinsp_rules->enable(rule_name, match_exact, enabled, ruleset_id);
m_k8s_audit_rules->enable(rule_name, match_exact, enabled, ruleset_id);
}
void falco_engine::enable_rule_exact(const string &rule_name, bool enabled)
{
enable_rule_exact(rule_name, enabled, m_default_ruleset);
for(auto &it : m_rulesets)
{
it.second->enable(rule_name, match_exact, enabled, ruleset_id);
}
}
void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled, const string &ruleset)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
m_sinsp_rules->enable_tags(tags, enabled, ruleset_id);
m_k8s_audit_rules->enable_tags(tags, enabled, ruleset_id);
}
void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled)
{
enable_rule_by_tag(tags, enabled, m_default_ruleset);
for(auto &it : m_rulesets)
{
it.second->enable_tags(tags, enabled, ruleset_id);
}
}
void falco_engine::set_min_priority(falco_common::priority_type priority)
@@ -271,68 +246,85 @@ uint64_t falco_engine::num_rules_for_ruleset(const std::string &ruleset)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
return m_sinsp_rules->num_rules_for_ruleset(ruleset_id) +
m_k8s_audit_rules->num_rules_for_ruleset(ruleset_id);
uint64_t ret = 0;
for(auto &it : m_rulesets)
{
ret += it.second->num_rules_for_ruleset(ruleset_id);
}
return ret;
}
void falco_engine::evttypes_for_ruleset(std::vector<bool> &evttypes, const std::string &ruleset)
void falco_engine::evttypes_for_ruleset(std::string &source, std::set<uint16_t> &evttypes, const std::string &ruleset)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
return m_sinsp_rules->evttypes_for_ruleset(evttypes, ruleset_id);
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
it->second->evttypes_for_ruleset(evttypes, ruleset_id);
}
void falco_engine::syscalls_for_ruleset(std::vector<bool> &syscalls, const std::string &ruleset)
std::shared_ptr<gen_event_formatter> falco_engine::create_formatter(const std::string &source,
const std::string &output)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
auto it = m_format_factories.find(source);
return m_sinsp_rules->syscalls_for_ruleset(syscalls, ruleset_id);
if(it == m_format_factories.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
return it->second->create_formatter(output);
}
unique_ptr<falco_engine::rule_result> falco_engine::process_sinsp_event(sinsp_evt *ev, uint16_t ruleset_id)
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::string &source, gen_event *ev, uint16_t ruleset_id)
{
if(should_drop_evt())
{
return unique_ptr<struct rule_result>();
}
if(!m_sinsp_rules->run(ev, ruleset_id))
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
if (!it->second->run(ev, ruleset_id))
{
return unique_ptr<struct rule_result>();
}
unique_ptr<struct rule_result> res(new rule_result());
res->source = "syscall";
res->source = source;
populate_rule_result(res, ev);
return res;
}
unique_ptr<falco_engine::rule_result> falco_engine::process_sinsp_event(sinsp_evt *ev)
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::string &source, gen_event *ev)
{
return process_sinsp_event(ev, m_default_ruleset_id);
return process_event(source, ev, m_default_ruleset_id);
}
unique_ptr<falco_engine::rule_result> falco_engine::process_k8s_audit_event(json_event *ev, uint16_t ruleset_id)
void falco_engine::add_source(const std::string &source,
std::shared_ptr<gen_event_filter_factory> filter_factory,
std::shared_ptr<gen_event_formatter_factory> formatter_factory)
{
if(should_drop_evt())
{
return unique_ptr<struct rule_result>();
}
m_filter_factories[source] = filter_factory;
m_format_factories[source] = formatter_factory;
// All k8s audit events have the single tag "1".
if(!m_k8s_audit_rules->run((gen_event *) ev, 1, ruleset_id))
{
return unique_ptr<struct rule_result>();
}
unique_ptr<struct rule_result> res(new rule_result());
res->source = "k8s_audit";
populate_rule_result(res, ev);
return res;
std::shared_ptr<falco_ruleset> ruleset(new falco_ruleset());
m_rulesets[source] = ruleset;
}
void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen_event *ev)
@@ -342,18 +334,29 @@ void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen
if(lua_isfunction(m_ls, -1))
{
lua_pushnumber(m_ls, ev->get_check_id());
if(lua_pcall(m_ls, 1, 4, 0) != 0)
if(lua_pcall(m_ls, 1, 5, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function output: " + string(lerr);
throw falco_exception(err);
}
const char *p = lua_tostring(m_ls, -4);
const char *p = lua_tostring(m_ls, -5);
res->rule = p;
res->evt = ev;
res->priority_num = (falco_common::priority_type) lua_tonumber(m_ls, -3);
res->format = lua_tostring(m_ls, -2);
res->priority_num = (falco_common::priority_type) lua_tonumber(m_ls, -4);
res->format = lua_tostring(m_ls, -3);
// Tags are passed back as a table, and is on the top of the stack
lua_pushnil(m_ls); /* first key */
while (lua_next(m_ls, -2) != 0) {
// key is at index -2, value is at index
// -1. We want the value.
res->tags.insert(luaL_checkstring(m_ls, -1));
// Remove value, keep key for next iteration
lua_pop(m_ls, 1);
}
lua_pop(m_ls, 1); // Clean table leftover
// Exception fields are passed back as a table
lua_pushnil(m_ls); /* first key */
@@ -374,84 +377,6 @@ void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen
}
}
bool falco_engine::parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top)
{
// Note that nlohmann::basic_json::value can throw nlohmann::basic_json::type_error (302, 306)
try
{
// If the object is an array, call parse_k8s_audit_json again for each item.
if(j.is_array())
{
if(top)
{
for(auto &item : j)
{
// Note we only handle a single top level array, to
// avoid excessive recursion.
if(! parse_k8s_audit_json(item, evts, false))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
// If the kind is EventList, split it into individual events
if(j.value("kind", "<NA>") == "EventList")
{
for(auto &je : j["items"])
{
evts.emplace_back();
je["kind"] = "Event";
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(je.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
std::string tmp;
sinsp_utils::ts_to_string(ns, &tmp, false, true);
evts.back().set_jevt(je, ns);
}
return true;
}
else if(j.value("kind", "<NA>") == "Event")
{
evts.emplace_back();
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(j.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
evts.back().set_jevt(j, ns);
return true;
}
else
{
return false;
}
}
catch(exception &e)
{
return false;
}
}
unique_ptr<falco_engine::rule_result> falco_engine::process_k8s_audit_event(json_event *ev)
{
return process_k8s_audit_event(ev, m_default_ruleset_id);
}
void falco_engine::describe_rule(string *rule)
{
return m_rules->describe_rule(rule);
@@ -478,29 +403,68 @@ void falco_engine::print_stats()
}
void falco_engine::add_sinsp_filter(string &rule,
set<uint32_t> &evttypes,
set<uint32_t> &syscalls,
set<string> &tags,
sinsp_filter* filter)
void falco_engine::add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<std::string> &tags)
{
m_sinsp_rules->add(rule, evttypes, syscalls, tags, filter);
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
it->second->add(rule, tags, filter);
}
void falco_engine::add_k8s_audit_filter(string &rule,
set<string> &tags,
json_event_filter* filter)
bool falco_engine::is_source_valid(const std::string &source)
{
// All k8s audit events have a single tag "1".
std::set<uint32_t> event_tags = {1};
return (m_rulesets.find(source) != m_rulesets.end());
}
m_k8s_audit_rules->add(rule, tags, event_tags, filter);
bool falco_engine::is_plugin_compatible(const std::string &name,
const std::string &version,
std::string &required_version)
{
sinsp_plugin::version plugin_version(version.c_str());
if(!plugin_version.m_valid)
{
throw falco_exception(string("Plugin version string ") + version + " not valid");
}
if(m_required_plugin_versions.find(name) == m_required_plugin_versions.end())
{
// No required engine versions, so no restrictions. Compatible.
return true;
}
for(auto &rversion : m_required_plugin_versions[name])
{
sinsp_plugin::version req_version(rversion.c_str());
if(req_version.m_version_major > plugin_version.m_version_major)
{
required_version = rversion;
return false;
}
}
return true;
}
void falco_engine::clear_filters()
{
m_sinsp_rules.reset(new falco_sinsp_ruleset());
m_k8s_audit_rules.reset(new falco_ruleset());
m_rulesets.clear();
for(auto &it : m_filter_factories)
{
std::shared_ptr<falco_ruleset> ruleset(new falco_ruleset());
m_rulesets[it.first] = ruleset;
}
m_required_plugin_versions.clear();
}
void falco_engine::set_sampling_ratio(uint32_t sampling_ratio)
@@ -534,23 +498,3 @@ inline bool falco_engine::should_drop_evt()
double coin = (random() * (1.0/RAND_MAX));
return (coin >= (1.0/(m_sampling_multiplier * m_sampling_ratio)));
}
sinsp_filter_factory &falco_engine::sinsp_factory()
{
if(!m_sinsp_factory)
{
throw falco_exception("No sinsp factory created yet");
}
return *(m_sinsp_factory.get());
}
json_event_filter_factory &falco_engine::json_factory()
{
if(!m_json_factory)
{
throw falco_exception("No json factory created yet");
}
return *(m_json_factory.get());
}

View File

@@ -28,10 +28,7 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "sinsp.h"
#include "filter.h"
#include "json_evt.h"
#include "gen_filter.h"
#include "rules.h"
#include "ruleset.h"
@@ -57,7 +54,8 @@ public:
static uint32_t engine_version();
// Print to stdout (using printf) a description of each field supported by this engine.
void list_fields(bool names_only=false);
// If source is non-empty, only fields for the provided source are printed.
void list_fields(std::string &source, bool verbose, bool names_only);
//
// Load rules either directly or from a filename.
@@ -80,25 +78,16 @@ public:
// be passed as an argument to process_event(). This allows
// for different sets of rules being active at once.
//
void enable_rule(const std::string &substring, bool enabled, const std::string &ruleset);
// Wrapper that assumes the default ruleset
void enable_rule(const std::string &substring, bool enabled);
void enable_rule(const std::string &substring, bool enabled, const std::string &ruleset = s_default_ruleset);
// Like enable_rule, but the rule name must be an exact match.
void enable_rule_exact(const std::string &rule_name, bool enabled, const std::string &ruleset);
// Wrapper that assumes the default ruleset
void enable_rule_exact(const std::string &rule_name, bool enabled);
void enable_rule_exact(const std::string &rule_name, bool enabled, const std::string &ruleset = s_default_ruleset);
//
// Enable/Disable any rules with any of the provided tags (set, exact matches only)
//
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled, const std::string &ruleset);
// Wrapper that assumes the default ruleset
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled);
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled, const std::string &ruleset = s_default_ruleset);
// Only load rules having this priority or more severe.
void set_min_priority(falco_common::priority_type priority);
@@ -152,8 +141,8 @@ public:
//
void set_extra(string &extra, bool replace_container_info);
// **Methods Related to k8s audit log events, which are
// **represented as json objects.
// Represents the result of matching an event against a set of
// rules.
struct rule_result {
gen_event *evt;
std::string rule;
@@ -161,18 +150,9 @@ public:
falco_common::priority_type priority_num;
std::string format;
std::set<std::string> exception_fields;
std::set<std::string> tags;
};
//
// Given a raw json object, return a list of k8s audit event
// objects that represent the object. This method handles
// things such as EventList splitting.
//
// Returns true if the json object was recognized as a k8s
// audit event(s), false otherwise.
//
bool parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top=true);
//
// Given an event, check it against the set of rules in the
// engine and if a matching rule is found, return details on
@@ -184,84 +164,81 @@ public:
// with a ruleset string.
//
// the returned rule_result is allocated and must be delete()d.
std::unique_ptr<rule_result> process_k8s_audit_event(json_event *ev, uint16_t ruleset_id);
std::unique_ptr<rule_result> process_event(std::string &source, gen_event *ev, uint16_t ruleset_id);
//
// Wrapper assuming the default ruleset
//
std::unique_ptr<rule_result> process_k8s_audit_event(json_event *ev);
std::unique_ptr<rule_result> process_event(std::string &source, gen_event *ev);
//
// Add a k8s_audit filter to the engine
// Configure the engine to support events with the provided
// source, with the provided filter factory and formatter factory.
//
void add_k8s_audit_filter(std::string &rule,
std::set<std::string> &tags,
json_event_filter* filter);
void add_source(const std::string &source,
std::shared_ptr<gen_event_filter_factory> filter_factory,
std::shared_ptr<gen_event_formatter_factory> formatter_factory);
// **Methods Related to Sinsp Events e.g system calls
//
// Given a ruleset, fill in a bitset containing the event
// types for which this ruleset can run.
//
void evttypes_for_ruleset(std::vector<bool> &evttypes, const std::string &ruleset);
// Return whether or not there is a valid filter/formatter
// factory for this source.
bool is_source_valid(const std::string &source);
//
// Given a ruleset, fill in a bitset containing the syscalls
// for which this ruleset can run.
// Add a filter for the provided event source to the engine
//
void syscalls_for_ruleset(std::vector<bool> &syscalls, const std::string &ruleset);
void add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<std::string> &tags);
//
// 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.
// Given an event source and ruleset, fill in a bitset
// containing the event types for which this ruleset can run.
//
// When ruleset_id is provided, use the enabled/disabled status
// associated with the provided ruleset. This is only useful
// when you have previously called enable_rule/enable_rule_by_tag
// with a ruleset string.
//
// the returned rule_result is allocated and must be delete()d.
std::unique_ptr<rule_result> process_sinsp_event(sinsp_evt *ev, uint16_t ruleset_id);
void evttypes_for_ruleset(std::string &source,
std::set<uint16_t> &evttypes,
const std::string &ruleset = s_default_ruleset);
//
// Wrapper assuming the default ruleset
// Given a source and output string, return an
// gen_event_formatter that can format output strings for an
// event.
//
std::unique_ptr<rule_result> process_sinsp_event(sinsp_evt *ev);
std::shared_ptr<gen_event_formatter> create_formatter(const std::string &source,
const std::string &output);
//
// Add a filter, which is related to the specified set of
// event types/syscalls, to the engine.
//
void add_sinsp_filter(std::string &rule,
std::set<uint32_t> &evttypes,
std::set<uint32_t> &syscalls,
std::set<std::string> &tags,
sinsp_filter* filter);
sinsp_filter_factory &sinsp_factory();
json_event_filter_factory &json_factory();
// Return whether the provided plugin name + version is
// compatible with the current set of loaded rules files.
// required_version will be filled in with the required
// version when the method returns false.
bool is_plugin_compatible(const std::string &name, const std::string &version, std::string &required_version);
private:
static nlohmann::json::json_pointer k8s_audit_time;
//
// 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();
shared_ptr<sinsp_filter_factory> m_sinsp_factory;
shared_ptr<json_event_filter_factory> m_json_factory;
falco_rules *m_rules;
// Maps from event source to object that can generate filters from rules
std::map<std::string, std::shared_ptr<gen_event_filter_factory>> m_filter_factories;
// Maps from event source to object that can format output strings in rules
std::map<std::string, std::shared_ptr<gen_event_formatter_factory>> m_format_factories;
// Maps from event source to the set of rules for that event source
std::map<std::string, std::shared_ptr<falco_ruleset>> m_rulesets;
std::unique_ptr<falco_rules> m_rules;
uint16_t m_next_ruleset_id;
std::map<string, uint16_t> m_known_rulesets;
falco_common::priority_type m_min_priority;
std::unique_ptr<falco_sinsp_ruleset> m_sinsp_rules;
std::unique_ptr<falco_ruleset> m_k8s_audit_rules;
// Maps from plugin to a list of required plugin versions
// found in any loaded rules files.
std::map<std::string, std::list<std::string>> m_required_plugin_versions;
void populate_rule_result(unique_ptr<struct rule_result> &res, gen_event *ev);
@@ -288,8 +265,7 @@ private:
uint32_t m_sampling_ratio;
double m_sampling_multiplier;
std::string m_lua_main_filename = "rule_loader.lua";
std::string m_default_ruleset = "falco-default-ruleset";
static const std::string s_default_ruleset;
uint32_t m_default_ruleset_id;
std::string m_extra;

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2020 The Falco Authors.
Copyright (C) 2021 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// The version of rules/filter fields/etc supported by this falco
// The version of rules/filter fields/etc supported by this Falco
// engine.
#define FALCO_ENGINE_VERSION (9)
#define FALCO_ENGINE_VERSION (11)
// This is the result of running "falco --list -N | sha256sum" and
// represents the fields supported by this version of falco. It's used
// represents the fields supported by this version of Falco. It's used
// at build time to detect a changed set of fields.
#define FALCO_FIELDS_CHECKSUM "2f324e2e66d4b423f53600e7e0fcf2f0ff72e4a87755c490f2ae8f310aba9386"
#define FALCO_FIELDS_CHECKSUM "4de812495f8529ac20bda2b9774462b15911a51df293d59fe9ccb6b922fdeb9d"

View File

@@ -27,31 +27,6 @@ namespace falco
namespace utils
{
std::string wrap_text(const std::string& str, uint32_t initial_pos, uint32_t indent, uint32_t line_len)
{
std::string ret;
size_t len = str.size();
for(uint32_t l = 0; l < len; l++)
{
if(l % (line_len - indent) == 0 && l != 0)
{
ret += "\n";
for(uint32_t m = 0; m < indent; m++)
{
ret += " ";
}
}
ret += str.at(l);
}
ret += "\n";
return ret;
}
uint32_t hardware_concurrency()
{
auto hc = std::thread::hardware_concurrency();

View File

@@ -20,169 +20,54 @@ limitations under the License.
#include "falco_engine.h"
#include "banned.h" // This raises a compilation error when certain functions are used
sinsp *falco_formats::s_inspector = NULL;
falco_engine *falco_formats::s_engine = NULL;
bool falco_formats::s_json_output = false;
bool falco_formats::s_json_include_output_property = true;
std::unique_ptr<sinsp_evt_formatter_cache> falco_formats::s_formatters = NULL;
const static struct luaL_Reg ll_falco[] =
{
{"formatter", &falco_formats::lua_formatter},
{"free_formatter", &falco_formats::lua_free_formatter},
{NULL, NULL}};
void falco_formats::init(sinsp *inspector,
falco_engine *engine,
lua_State *ls,
bool json_output,
bool json_include_output_property)
falco_formats::falco_formats(falco_engine *engine,
bool json_include_output_property,
bool json_include_tags_property)
: m_falco_engine(engine),
m_json_include_output_property(json_include_output_property),
m_json_include_tags_property(json_include_tags_property)
{
s_inspector = inspector;
s_engine = engine;
s_json_output = json_output;
s_json_include_output_property = json_include_output_property;
// todo(leogr): we should have used std::make_unique, but we cannot since it's not C++14
s_formatters = std::unique_ptr<sinsp_evt_formatter_cache>(new sinsp_evt_formatter_cache(s_inspector));
luaL_openlib(ls, "formats", ll_falco, 0);
}
int falco_formats::lua_formatter(lua_State *ls)
falco_formats::~falco_formats()
{
string source = luaL_checkstring(ls, -2);
string format = luaL_checkstring(ls, -1);
try
{
if(source == "syscall")
{
sinsp_evt_formatter *formatter;
formatter = new sinsp_evt_formatter(s_inspector, format);
lua_pushnil(ls);
lua_pushlightuserdata(ls, formatter);
}
else
{
json_event_formatter *formatter;
formatter = new json_event_formatter(s_engine->json_factory(), format);
lua_pushnil(ls);
lua_pushlightuserdata(ls, formatter);
}
}
catch(exception &e)
{
std::ostringstream os;
os << "Invalid output format '"
<< format
<< "': '"
<< e.what()
<< "'";
lua_pushstring(ls, os.str().c_str());
lua_pushnil(ls);
}
return 2;
}
int falco_formats::lua_free_formatter(lua_State *ls)
string falco_formats::format_event(gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format, std::set<std::string> &tags)
{
if(!lua_islightuserdata(ls, -1) ||
!lua_isstring(ls, -2))
{
luaL_error(ls, "Invalid argument passed to free_formatter");
}
string source = luaL_checkstring(ls, -2);
if(source == "syscall")
{
sinsp_evt_formatter *formatter = (sinsp_evt_formatter *)lua_topointer(ls, -1);
delete(formatter);
}
else
{
json_event_formatter *formatter = (json_event_formatter *)lua_topointer(ls, -1);
delete(formatter);
}
return 0;
}
string falco_formats::format_event(const gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format)
{
string line;
string json_line;
string sformat = format;
if(strcmp(source.c_str(), "syscall") == 0)
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_falco_engine->create_formatter(source, format);
// Format the original output string, regardless of output format
formatter->tostring_withformat(evt, line, gen_event_formatter::OF_NORMAL);
if(formatter->get_output_format() == gen_event_formatter::OF_JSON)
{
// This is "output"
s_formatters->tostring((sinsp_evt *)evt, sformat, &line);
string json_line;
if(s_json_output)
// Format the event into a json object with all fields resolved
formatter->tostring(evt, json_line);
// The formatted string might have a leading newline. If it does, remove it.
if(json_line[0] == '\n')
{
sinsp_evt::param_fmt cur_fmt = s_inspector->get_buffer_format();
switch(cur_fmt)
{
case sinsp_evt::PF_NORMAL:
s_inspector->set_buffer_format(sinsp_evt::PF_JSON);
break;
case sinsp_evt::PF_EOLS:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONEOLS);
break;
case sinsp_evt::PF_HEX:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONHEX);
break;
case sinsp_evt::PF_HEXASCII:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONHEXASCII);
break;
case sinsp_evt::PF_BASE64:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONBASE64);
break;
default:
// do nothing
break;
}
// This is output fields
s_formatters->tostring((sinsp_evt *)evt, sformat, &json_line);
// The formatted string might have a leading newline. If it does, remove it.
if(json_line[0] == '\n')
{
json_line.erase(0, 1);
}
s_inspector->set_buffer_format(cur_fmt);
json_line.erase(0, 1);
}
}
else
{
json_event_formatter formatter(s_engine->json_factory(), sformat);
line = formatter.tostring((json_event *)evt);
if(s_json_output)
{
json_line = formatter.tojson((json_event *)evt);
}
}
// For JSON output, the formatter returned a json-as-text
// object containing all the fields in the original format
// message as well as the event time in ns. Use this to build
// a more detailed object containing the event time, rule,
// severity, full output, and fields.
if(s_json_output)
{
// For JSON output, the formatter returned a json-as-text
// object containing all the fields in the original format
// message as well as the event time in ns. Use this to build
// a more detailed object containing the event time, rule,
// severity, full output, and fields.
Json::Value event;
Json::Value rule_tags;
Json::FastWriter writer;
string full_line;
unsigned int rule_tags_idx = 0;
// Convert the time-as-nanoseconds to a more json-friendly ISO8601.
time_t evttime = evt->get_ts() / 1000000000;
@@ -197,13 +82,31 @@ string falco_formats::format_event(const gen_event *evt, const std::string &rule
event["time"] = iso8601evttime;
event["rule"] = rule;
event["priority"] = level;
event["source"] = source;
if(s_json_include_output_property)
if(m_json_include_output_property)
{
// This is the filled-in output line.
event["output"] = line;
}
if(m_json_include_tags_property)
{
if (tags.size() == 0)
{
// This sets an empty array
rule_tags = Json::arrayValue;
}
else
{
for (auto &tag : tags)
{
rule_tags[rule_tags_idx++] = tag;
}
}
event["tags"] = rule_tags;
}
full_line = writer.write(event);
// Json::FastWriter may add a trailing newline. If it
@@ -226,19 +129,19 @@ string falco_formats::format_event(const gen_event *evt, const std::string &rule
return line.c_str();
}
map<string, string> falco_formats::resolve_tokens(const gen_event *evt, const std::string &source, const std::string &format)
map<string, string> falco_formats::get_field_values(gen_event *evt, const std::string &source,
const std::string &format)
{
string sformat = format;
map<string, string> values;
if(source == "syscall")
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_falco_engine->create_formatter(source, format);
map<string, string> ret;
if (! formatter->get_field_values(evt, ret))
{
s_formatters->resolve_tokens((sinsp_evt *)evt, sformat, values);
throw falco_exception("Could not extract all field values from event");
}
// k8s_audit
else
{
json_event_formatter json_formatter(s_engine->json_factory(), sformat);
values = json_formatter.tomap((json_event *)evt);
}
return values;
return ret;
}

View File

@@ -16,7 +16,8 @@ limitations under the License.
#pragma once
#include "sinsp.h"
#include <string>
#include <map>
extern "C"
{
@@ -25,35 +26,26 @@ extern "C"
#include "lauxlib.h"
}
#include "json_evt.h"
#include "falco_engine.h"
#include <gen_filter.h>
class sinsp_evt_formatter;
#include "falco_engine.h"
class falco_formats
{
public:
static void init(sinsp *inspector,
falco_engine *engine,
lua_State *ls,
bool json_output,
bool json_include_output_property);
falco_formats(falco_engine *engine,
bool json_include_output_property,
bool json_include_tags_property);
virtual ~falco_formats();
// formatter = falco.formatter(format_string)
static int lua_formatter(lua_State *ls);
std::string format_event(gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format, std::set<std::string> &tags);
// falco.free_formatter(formatter)
static int lua_free_formatter(lua_State *ls);
map<string, string> get_field_values(gen_event *evt, const std::string &source,
const std::string &format);
static string format_event(const gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format);
static map<string, string> resolve_tokens(const gen_event *evt, const std::string &source,
const std::string &format);
static sinsp *s_inspector;
static falco_engine *s_engine;
static std::unique_ptr<sinsp_evt_formatter_cache> s_formatters;
static bool s_json_output;
static bool s_json_include_output_property;
protected:
falco_engine *m_falco_engine;
bool m_json_include_output_property;
bool m_json_include_tags_property;
};

View File

@@ -50,6 +50,81 @@ uint64_t json_event::get_ts() const
return m_event_ts;
}
static nlohmann::json::json_pointer k8s_audit_time = "/stageTimestamp"_json_pointer;
bool falco_k8s_audit::parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top)
{
// Note that nlohmann::basic_json::value can throw nlohmann::basic_json::type_error (302, 306)
try
{
// If the object is an array, call parse_k8s_audit_json again for each item.
if(j.is_array())
{
if(top)
{
for(auto &item : j)
{
// Note we only handle a single top level array, to
// avoid excessive recursion.
if(! parse_k8s_audit_json(item, evts, false))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
// If the kind is EventList, split it into individual events
if(j.value("kind", "<NA>") == "EventList")
{
for(auto &je : j["items"])
{
evts.emplace_back();
je["kind"] = "Event";
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(je.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
std::string tmp;
sinsp_utils::ts_to_string(ns, &tmp, false, true);
evts.back().set_jevt(je, ns);
}
return true;
}
else if(j.value("kind", "<NA>") == "Event")
{
evts.emplace_back();
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(j.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
evts.back().set_jevt(j, ns);
return true;
}
else
{
return false;
}
}
catch(exception &e)
{
return false;
}
}
json_event_value::json_event_value()
{
}
@@ -615,7 +690,7 @@ size_t json_event_filter_check::parsed_size()
}
}
json_event_filter_check::check_info &json_event_filter_check::get_fields()
json_event_filter_check::check_info &json_event_filter_check::get_info()
{
return m_info;
}
@@ -1357,7 +1432,7 @@ json_event_filter_factory::json_event_filter_factory()
for(auto &chk : m_defined_checks)
{
m_info.push_back(chk->get_fields());
m_info.push_back(chk->get_info());
}
}
@@ -1389,36 +1464,99 @@ gen_event_filter_check *json_event_filter_factory::new_filtercheck(const char *f
return NULL;
}
std::list<json_event_filter_check::check_info> &json_event_filter_factory::get_fields()
std::list<gen_event_filter_factory::filter_fieldclass_info> json_event_filter_factory::get_fields()
{
return m_info;
std::list<gen_event_filter_factory::filter_fieldclass_info> ret;
// It's not quite copy to the public information, as m_info
// has addl info about indexing. That info is added to the
// description.
for(auto &chk: m_defined_checks)
{
json_event_filter_check::check_info &info = chk->get_info();
gen_event_filter_factory::filter_fieldclass_info cinfo;
cinfo.name = info.m_name;
cinfo.desc = info.m_desc;
cinfo.shortdesc = info.m_shortdesc;
for(auto &field : info.m_fields)
{
gen_event_filter_factory::filter_field_info info;
info.name = field.m_name;
info.desc = field.m_desc;
// All json fields work on strings
info.data_type = "CHARBUF";
switch(field.m_idx_mode)
{
case json_event_filter_check::IDX_REQUIRED:
case json_event_filter_check::IDX_ALLOWED:
info.tags.insert(json_event_filter_check::s_index_mode_strs[field.m_idx_mode]);
break;
case json_event_filter_check::IDX_NONE:
default:
break;
};
cinfo.fields.emplace_back(std::move(info));
}
ret.emplace_back(std::move(cinfo));
}
return ret;
}
json_event_formatter::json_event_formatter(json_event_filter_factory &json_factory, std::string &format):
m_format(format),
m_json_factory(json_factory)
json_event_formatter::json_event_formatter(std::shared_ptr<gen_event_filter_factory> json_factory)
: m_output_format(OF_NORMAL), m_json_factory(json_factory)
{
parse_format();
}
json_event_formatter::~json_event_formatter()
{
}
std::string json_event_formatter::tostring(json_event *ev)
void json_event_formatter::set_format(output_format of, const std::string &format)
{
m_output_format = of;
m_format = format;
parse_format();
}
bool json_event_formatter::tostring_withformat(gen_event *gevt, std::string &output, gen_event_formatter::output_format of)
{
json_event *ev = static_cast<json_event *>(gevt);
std::string ret;
std::list<std::pair<std::string, std::string>> resolved;
resolve_tokens(ev, resolved);
for(auto &res : resolved)
if(of == OF_JSON)
{
ret += res.second;
ret = tojson(ev);
return true;
}
else
{
std::list<std::pair<std::string, std::string>> resolved;
return ret;
resolve_format(ev, resolved);
output = "";
for(auto &res : resolved)
{
output += res.second;
}
return true;
}
}
bool json_event_formatter::tostring(gen_event *gevt, std::string &output)
{
return tostring_withformat(gevt, output, m_output_format);
}
std::string json_event_formatter::tojson(json_event *ev)
@@ -1428,7 +1566,7 @@ std::string json_event_formatter::tojson(json_event *ev)
std::list<std::pair<std::string, std::string>> resolved;
resolve_tokens(ev, resolved);
resolve_format(ev, resolved);
for(auto &res : resolved)
{
@@ -1443,12 +1581,13 @@ std::string json_event_formatter::tojson(json_event *ev)
return ret.dump();
}
std::map<std::string, std::string> json_event_formatter::tomap(json_event *ev)
bool json_event_formatter::get_field_values(gen_event *gevt, std::map<std::string, std::string> &fields)
{
std::map<std::string, std::string> ret;
json_event *ev = static_cast<json_event *>(gevt);
std::list<std::pair<std::string, std::string>> res;
resolve_tokens(ev, res);
resolve_format(ev, res);
for(auto &r : res)
{
@@ -1459,11 +1598,11 @@ std::map<std::string, std::string> json_event_formatter::tomap(json_event *ev)
{
r.second = "<NA>";
}
ret.insert(r);
fields.insert(r);
}
}
return ret;
return true;
}
void json_event_formatter::parse_format()
@@ -1485,7 +1624,7 @@ void json_event_formatter::parse_format()
{
// Skip the %
tformat.erase(0, 1);
json_event_filter_check *chk = (json_event_filter_check *)m_json_factory.new_filtercheck(tformat.c_str());
json_event_filter_check *chk = (json_event_filter_check *)m_json_factory->new_filtercheck(tformat.c_str());
if(!chk)
{
@@ -1521,7 +1660,7 @@ void json_event_formatter::parse_format()
}
}
void json_event_formatter::resolve_tokens(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved)
void json_event_formatter::resolve_format(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved)
{
for(auto tok : m_tokens)
{
@@ -1561,3 +1700,43 @@ void json_event_formatter::resolve_tokens(json_event *ev, std::list<std::pair<st
}
}
}
gen_event_formatter::output_format json_event_formatter::get_output_format()
{
return m_output_format;
}
json_event_formatter_factory::json_event_formatter_factory(std::shared_ptr<gen_event_filter_factory> json_factory)
: m_output_format(gen_event_formatter::OF_NORMAL), m_json_factory(json_factory)
{
}
json_event_formatter_factory::~json_event_formatter_factory()
{
}
void json_event_formatter_factory::set_output_format(gen_event_formatter::output_format of)
{
m_formatters.clear();
m_output_format = of;
}
std::shared_ptr<gen_event_formatter> json_event_formatter_factory::create_formatter(const std::string &format)
{
auto it = m_formatters.find(format);
if (it != m_formatters.end())
{
return it->second;
}
std::shared_ptr<gen_event_formatter> ret;
ret.reset(new json_event_formatter(m_json_factory));
ret->set_format(m_output_format, format);
m_formatters[format] = ret;
return ret;
}

View File

@@ -57,6 +57,19 @@ protected:
uint64_t m_event_ts;
};
namespace falco_k8s_audit {
//
// Given a raw json object, return a list of k8s audit event
// objects that represent the object. This method handles
// things such as EventList splitting.
//
// Returns true if the json object was recognized as a k8s
// audit event(s), false otherwise.
//
bool parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top=true);
};
// A class representing an extracted value or a value on the rhs of a
// filter_check. This intentionally doesn't use the same types as
// ppm_events_public.h to take advantage of actual classes instead of
@@ -153,8 +166,8 @@ public:
struct check_info
{
std::string m_name;
std::string m_shortdesc;
std::string m_desc;
std::string m_class_info;
std::list<field_info> m_fields;
};
@@ -176,7 +189,7 @@ public:
// brackets (e.g. ka.image[foo])
size_t parsed_size();
check_info &get_fields();
check_info &get_info();
//
// Allocate a new check of the same type. Must be overridden.
@@ -386,29 +399,41 @@ public:
gen_event_filter_check *new_filtercheck(const char *fldname);
// All defined field names
std::list<json_event_filter_check::check_info> &get_fields();
std::list<gen_event_filter_factory::filter_fieldclass_info> get_fields();
private:
std::list<std::shared_ptr<json_event_filter_check>> m_defined_checks;
std::list<json_event_filter_check::check_info> m_info;
};
// Unlike the other classes, this does not inherit from a shared class
// that's used both by json events and sinsp events. It might be
// worthwhile, but it would require a lot of additional work to pull
// up functionality into the generic filtercheck class.
class json_event_formatter
class json_event_formatter : public gen_event_formatter
{
public:
json_event_formatter(json_event_filter_factory &factory, std::string &format);
json_event_formatter(std::shared_ptr<gen_event_filter_factory> factory);
virtual ~json_event_formatter();
std::string tostring(json_event *ev);
std::string tojson(json_event *ev);
std::map<std::string, std::string> tomap(json_event *ev);
void set_format(output_format of, const std::string &format) override;
bool tostring(gen_event *evt, std::string &output) override;
bool tostring_withformat(gen_event *evt, std::string &output, gen_event_formatter::output_format of) override;
bool get_field_values(gen_event *evt, std::map<std::string, std::string> &fields) override;
output_format get_output_format() override;
void resolve_tokens(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved);
std::string tojson(json_event *ev);
// Split the format string into a list of tuples, broken at
// output fields, where each tuple is either a block of text
// from the original format string, or a field value/pair from
// the original format string.
//
// For example, given a format string "some output
// (%proc.name)", this will fill in resolved with 3 tuples:
// - ["", "some output ("]
// - ["proc.name", "nginx"]
// - ["", ")"]
//
// This can be used either to return a resolved output string
// or a map of field name/value pairs.
void resolve_format(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved);
private:
void parse_format();
@@ -428,6 +453,8 @@ private:
std::shared_ptr<json_event_filter_check> check;
};
gen_event_formatter::output_format m_output_format;
// The original format string
std::string m_format;
@@ -436,5 +463,25 @@ private:
std::list<fmt_token> m_tokens;
// All the filterchecks required to resolve tokens in the format string
json_event_filter_factory &m_json_factory;
std::shared_ptr<gen_event_filter_factory> m_json_factory;
};
class json_event_formatter_factory : public gen_event_formatter_factory
{
public:
json_event_formatter_factory(std::shared_ptr<gen_event_filter_factory> factory);
virtual ~json_event_formatter_factory();
void set_output_format(gen_event_formatter::output_format of) override;
std::shared_ptr<gen_event_formatter> create_formatter(const std::string &format) override;
protected:
// Maps from output string to formatter
std::map<std::string, std::shared_ptr<gen_event_formatter>> m_formatters;
gen_event_formatter::output_format m_output_format;
// All the filterchecks required to resolve tokens in the format string
std::shared_ptr<gen_event_filter_factory> m_json_factory;
};

View File

@@ -0,0 +1,21 @@
#
# Copyright (C) 2021 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
file(GLOB_RECURSE lua_module_files ${CMAKE_CURRENT_SOURCE_DIR} *.lua)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/falco_engine_lua_files.cpp
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/lua-to-cpp.sh ${CMAKE_CURRENT_SOURCE_DIR} ${LYAML_LUA_DIR} ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${lua_files} ${CMAKE_CURRENT_SOURCE_DIR}/lua-to-cpp.sh lyaml)
add_library(luafiles falco_engine_lua_files.cpp)
target_include_directories(luafiles PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,84 @@
#!/bin/bash
set -euo pipefail
LUA_FILE_DIR=$1
LYAML_LUA_DIR=$2
OUTPUT_DIR=$3
MODULE_SYMS=()
CODE_SYMS=()
function add_lua_file {
filename=$1
is_module=$2
# Take the basename of the file
BASE_NAME=$(basename ${file} .lua)
SYMBOL_NAME="${BASE_NAME}_lua_file_contents"
FILE_CONTENTS=$(<${file})
# Add a symbol to the .cc file containing the contents of the file
echo "const char *${SYMBOL_NAME}=R\"LUAFILE(${FILE_CONTENTS})LUAFILE\";" >> ${OUTPUT_DIR}/falco_engine_lua_files.cpp
# Add an extern reference to the .hh file
echo "extern const char *${SYMBOL_NAME};" >> ${OUTPUT_DIR}/falco_engine_lua_files.hh
if [[ "${is_module}" == "true" ]]; then
# Determine the module name for the file
if [[ "${file}" == *"/"* ]]; then
MODULE_NAME=$(echo ${file} | tr / . | sed -e 's/.lua//')
else
MODULE_NAME=$(basename ${file} .lua)
fi
# Add the pair (string contents, module name) to MODULE_SYMS
PAIR=$(echo "{${SYMBOL_NAME},\"${MODULE_NAME}\"}")
MODULE_SYMS+=(${PAIR})
else
# Add the string to CODE_SYMS
CODE_SYMS+=(${SYMBOL_NAME})
fi
}
cat <<EOF > ${OUTPUT_DIR}/falco_engine_lua_files.cpp
// Automatically generated. Do not edit
#include "falco_engine_lua_files.hh"
EOF
cat <<EOF > ${OUTPUT_DIR}/falco_engine_lua_files.hh
#pragma once
// Automatically generated. Do not edit
#include <list>
#include <utility>
EOF
# lyaml and any files in the "modules" subdirectory are treated as lua
# modules.
pushd ${LYAML_LUA_DIR}
for file in *.lua */*.lua; do
add_lua_file $file "true"
done
popd
pushd ${LUA_FILE_DIR}/modules
for file in *.lua; do
add_lua_file $file "true"
done
popd
# Any .lua files in this directory are treated as code with functions
# to execute.
pushd ${LUA_FILE_DIR}
for file in ${LUA_FILE_DIR}/*.lua; do
add_lua_file $file "false"
done
popd
# Create a list of lua module (string, module name) pairs from MODULE_SYMS
echo "extern std::list<std::pair<const char *,const char *>> lua_module_strings;" >> ${OUTPUT_DIR}/falco_engine_lua_files.hh
echo "std::list<std::pair<const char *,const char *>> lua_module_strings = {$(IFS=, ; echo "${MODULE_SYMS[*]}")};" >> ${OUTPUT_DIR}/falco_engine_lua_files.cpp
# Create a list of lua code strings from CODE_SYMS
echo "extern std::list<const char *> lua_code_strings;" >> ${OUTPUT_DIR}/falco_engine_lua_files.hh
echo "std::list<const char *> lua_code_strings = {$(IFS=, ; echo "${CODE_SYMS[*]}")};" >> ${OUTPUT_DIR}/falco_engine_lua_files.cpp

View File

@@ -18,7 +18,7 @@
Much of the scaffolding and helpers was derived from Andre Murbach Maidl's Lua parser (https://github.com/andremm/lua-parser).
While this is based on the sysdig filtering syntax (*), the Falco syntax is extended to support "macro" terms, which are just identifiers.
While this is based on the falcosecurity-libs filtering syntax (*), the Falco syntax is extended to support "macro" terms, which are just identifiers.
(*) There is currently one known difference with the syntax implemented in libsinsp: In libsinsp, field names cannot start with 'a', 'o', or 'n'. With this parser they can.
@@ -196,9 +196,9 @@ local G = {
Identifier = V "idStart" * V "idRest" ^ 0,
Macro = V "idStart" * V "idRest" ^ 0 * -P ".",
Int = digit ^ 1,
PathString = (alnum + S ",.-_/*?") ^ 1,
ArgString = (alnum + S ",.-_/*?~") ^ 1,
PortRangeString = (V "Int" + S ":,") ^ 1,
Index = V "PortRangeString" + V "Int" + V "PathString",
Index = V "PortRangeString" + V "Int" + V "ArgString",
FieldName = V "Identifier" * (P "." + V "Identifier") ^ 1 * (P "[" * V "Index" * P "]") ^ -1,
Name = C(V "Identifier") * -V "idRest",
Hex = (P("0x") + P("0X")) * xdigit ^ 1,

View File

@@ -20,7 +20,6 @@
--]]
local sinsp_rule_utils = require "sinsp_rule_utils"
local compiler = require "compiler"
local yaml = require"lyaml"
@@ -69,7 +68,7 @@ priorities = {
--[[
Take a filter AST and set it up in the libsinsp runtime, using the filter API.
--]]
local function install_filter(node, filter_api_lib, lua_parser, parent_bool_op)
local function create_filter_obj(node, lua_parser, parent_bool_op)
local t = node.type
if t == "BinaryBoolOp" then
@@ -78,41 +77,84 @@ local function install_filter(node, filter_api_lib, lua_parser, parent_bool_op)
-- never necessary when we have identical successive operators. so we
-- avoid it as a runtime performance optimization.
if (not(node.operator == parent_bool_op)) then
filter_api_lib.nest(lua_parser) -- io.write("(")
err = filter.nest(lua_parser) -- io.write("(")
if err ~= nil then
return err
end
end
install_filter(node.left, filter_api_lib, lua_parser, node.operator)
filter_api_lib.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
install_filter(node.right, filter_api_lib, lua_parser, node.operator)
err = create_filter_obj(node.left, lua_parser, node.operator)
if err ~= nil then
return err
end
err = filter.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
if err ~= nil then
return err
end
err = create_filter_obj(node.right, lua_parser, node.operator)
if err ~= nil then
return err
end
if (not (node.operator == parent_bool_op)) then
filter_api_lib.unnest(lua_parser) -- io.write(")")
err = filter.unnest(lua_parser) -- io.write(")")
if err ~= nil then
return err
end
end
elseif t == "UnaryBoolOp" then
filter_api_lib.nest(lua_parser) --io.write("(")
filter_api_lib.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
install_filter(node.argument, filter_api_lib, lua_parser)
filter_api_lib.unnest(lua_parser) -- io.write(")")
err = filter.nest(lua_parser) --io.write("(")
if err ~= nil then
return err
end
err = filter.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
if err ~= nil then
return err
end
err = create_filter_obj(node.argument, lua_parser)
if err ~= nil then
return err
end
err = filter.unnest(lua_parser) -- io.write(")")
if err ~= nil then
return err
end
elseif t == "BinaryRelOp" then
if (node.operator == "in" or
node.operator == "intersects" or
node.operator == "pmatch") then
elements = map(function (el) return el.value end, node.right.elements)
filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, elements, node.index)
err = filter.rel_expr(lua_parser, node.left.value, node.operator, elements, node.index)
if err ~= nil then
return err
end
else
filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, node.right.value, node.index)
err = filter.rel_expr(lua_parser, node.left.value, node.operator, node.right.value, node.index)
if err ~= nil then
return err
end
end
-- io.write(node.left.value.." "..node.operator.." "..node.right.value)
elseif t == "UnaryRelOp" then
filter_api_lib.rel_expr(lua_parser, node.argument.value, node.operator, node.index)
err = filter.rel_expr(lua_parser, node.argument.value, node.operator, node.index)
if err ~= nil then
return err
end
--io.write(node.argument.value.." "..node.operator)
else
error ("Unexpected type in install_filter: "..t)
return "Unexpected type in create_filter_obj: "..t
end
return nil
end
function set_output(output_format, state)
@@ -310,7 +352,7 @@ function build_error_with_context(ctx, err)
return {ret}
end
function validate_exception_item_multi_fields(eitem, context)
function validate_exception_item_multi_fields(rules_mgr, source, eitem, context)
local name = eitem['name']
local fields = eitem['fields']
@@ -329,7 +371,7 @@ function validate_exception_item_multi_fields(eitem, context)
end
end
for k, fname in ipairs(fields) do
if not is_defined_filter(fname) then
if not falco_rules.is_defined_field(rules_mgr, source, fname) then
return false, build_error_with_context(context, "Rule exception item "..name..": field name "..fname.." is not a supported filter field"), warnings
end
end
@@ -340,7 +382,7 @@ function validate_exception_item_multi_fields(eitem, context)
end
end
function validate_exception_item_single_field(eitem, context)
function validate_exception_item_single_field(rules_mgr, source, eitem, context)
local name = eitem['name']
local fields = eitem['fields']
@@ -355,7 +397,7 @@ function validate_exception_item_single_field(eitem, context)
return false, build_error_with_context(context, "Rule exception item "..name..": fields and comps must both be strings"), warnings
end
end
if not is_defined_filter(fields) then
if not falco_rules.is_defined_field(rules_mgr, source, fields) then
return false, build_error_with_context(context, "Rule exception item "..name..": field name "..fields.." is not a supported filter field"), warnings
end
if defined_comp_operators[comps] == nil then
@@ -363,37 +405,6 @@ function validate_exception_item_single_field(eitem, context)
end
end
function is_defined_filter(filter)
if defined_noarg_filters[filter] ~= nil then
return true
else
bracket_idx = string.find(filter, "[", 1, true)
if bracket_idx ~= nil then
subfilter = string.sub(filter, 1, bracket_idx-1)
if defined_arg_filters[subfilter] ~= nil then
return true
end
end
dot_idx = string.find(filter, ".", 1, true)
while dot_idx ~= nil do
subfilter = string.sub(filter, 1, dot_idx-1)
if defined_arg_filters[subfilter] ~= nil then
return true
end
dot_idx = string.find(filter, ".", dot_idx+1, true)
end
end
return false
end
function load_rules_doc(rules_mgr, doc, load_state)
local warnings = {}
@@ -426,6 +437,29 @@ function load_rules_doc(rules_mgr, doc, load_state)
return false, build_error_with_context(v['context'], "Rules require engine version "..v['required_engine_version']..", but engine version is "..falco_rules.engine_version(rules_mgr)), warnings
end
elseif (v['required_plugin_versions']) then
for _, vobj in ipairs(v['required_plugin_versions']) do
if vobj['name'] == nil then
return false, build_error_with_context(v['context'], "required_plugin_versions item must have name property"), warnings
end
if vobj['version'] == nil then
return false, build_error_with_context(v['context'], "required_plugin_versions item must have version property"), warnings
end
-- In the rules yaml, it's a name + version. But it's
-- possible, although unlikely, that a single yaml blob
-- contains multiple docs, withe each doc having its own
-- required_engine_version entry. So populate a map plugin
-- name -> list of required plugin versions.
if load_state.required_plugin_versions[vobj['name']] == nil then
load_state.required_plugin_versions[vobj['name']] = {}
end
table.insert(load_state.required_plugin_versions[vobj['name']], vobj['version'])
end
elseif (v['macro']) then
if (v['macro'] == nil or type(v['macro']) == "table") then
@@ -508,10 +542,6 @@ function load_rules_doc(rules_mgr, doc, load_state)
return false, build_error_with_context(v['context'], "Rule name is empty"), warnings
end
if (v['condition'] == nil and v['exceptions'] == nil) then
return false, build_error_with_context(v['context'], "Rule must have exceptions or condition property"), warnings
end
-- By default, if a rule's condition refers to an unknown
-- filter like evt.type, etc the loader throws an error.
if v['skip-if-unknown-filter'] == nil then
@@ -558,9 +588,9 @@ function load_rules_doc(rules_mgr, doc, load_state)
-- Different handling if the fields property is a single item vs a list
local valid, err
if type(eitem['fields']) == "table" then
valid, err = validate_exception_item_multi_fields(eitem, v['context'])
valid, err = validate_exception_item_multi_fields(rules_mgr, v['source'], eitem, v['context'])
else
valid, err = validate_exception_item_single_field(eitem, v['context'])
valid, err = validate_exception_item_single_field(rules_mgr, v['source'], eitem, v['context'])
end
if valid == false then
@@ -577,45 +607,82 @@ function load_rules_doc(rules_mgr, doc, load_state)
return false, build_error_with_context(v['context'], "Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists"), warnings
end
else
if (v['condition'] == nil and next(v['exceptions']) == nil) then
return false, build_error_with_context(v['context'], "Appended rule must have exceptions or condition property"), warnings
end
if next(v['exceptions']) ~= nil then
for _, eitem in ipairs(v['exceptions']) do
local name = eitem['name']
local fields = eitem['fields']
local comps = eitem['comps']
if name == nil then
if eitem['name'] == nil then
return false, build_error_with_context(v['context'], "Rule exception item must have name property"), warnings
end
-- You can't append exception fields or comps to a rule
if fields ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception fields to existing rule, only values"), warnings
-- Seperate case when a exception name is not found
-- This means that a new exception is being appended
local new_exception = true
for _, rex_item in ipairs(state.rules_by_name[v['rule']]['exceptions']) do
if rex_item['name'] == eitem['name'] then
new_exception = false
break
end
end
if comps ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception comps to existing rule, only values"), warnings
end
if new_exception then
local exceptions = state.rules_by_name[v['rule']]['exceptions']
if eitem['fields'] == nil then
return false, build_error_with_context(v['context'], "Rule exception new item "..eitem['name']..": must have fields property with a list of fields"), warnings
end
if eitem['values'] == nil then
return false, build_error_with_context(v['context'], "Rule exception new item "..eitem['name']..": must have values property with a list of values"), warnings
end
local valid, err
if type(eitem['fields']) == "table" then
valid, err = validate_exception_item_multi_fields(rules_mgr, v['source'], eitem, v['context'])
else
valid, err = validate_exception_item_single_field(rules_mgr, v['source'], eitem, v['context'])
end
if valid == false then
return valid, err, warnings
end
-- You can append values. They are added to the
-- corresponding name, if it exists. If no
-- exception with that name exists, add a
-- warning.
if eitem['values'] ~= nil then
local found=false
for _, reitem in ipairs(state.rules_by_name[v['rule']]['exceptions']) do
if reitem['name'] == eitem['name'] then
found=true
for _, values in ipairs(eitem['values']) do
reitem['values'][#reitem['values'] + 1] = values
-- Insert the complete exception object
exceptions[#exceptions+1] = eitem
else
-- Appends to existing exception here
-- You can't append exception fields or comps to an existing rule exception
if eitem['fields'] ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception fields to existing rule, only values"), warnings
end
if eitem['comps'] ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception comps to existing rule, only values"), warnings
end
-- You can append values. They are added to the
-- corresponding name, if it exists. If no
-- exception with that name exists, add a
-- warning.
if eitem['values'] ~= nil then
local found=false
for _, reitem in ipairs(state.rules_by_name[v['rule']]['exceptions']) do
if reitem['name'] == eitem['name'] then
found=true
for _, values in ipairs(eitem['values']) do
reitem['values'][#reitem['values'] + 1] = values
end
end
end
end
if found == false then
warnings[#warnings + 1] = "Rule "..v['rule'].." with append=true: no set of fields matching name "..eitem['name']
end
if found == false then
warnings[#warnings + 1] = "Rule "..v['rule'].." with append=true: no set of fields matching name "..eitem['name']
end
end
end
end
end
@@ -629,14 +696,25 @@ function load_rules_doc(rules_mgr, doc, load_state)
end
else
local err = nil
for j, field in ipairs({'condition', 'output', 'desc', 'priority'}) do
if (v[field] == nil) then
return false, build_error_with_context(v['context'], "Rule must have property "..field), warnings
if (err == nil and v[field] == nil) then
err = build_error_with_context(v['context'], "Rule must have property "..field)
end
end
-- Convert the priority-as-string to a priority-as-number now
-- Handle spacial case where "enabled" flag is defined only
if (err ~= nil) then
if (v['enabled'] == nil) then
return false, err, warnings
else
if state.rules_by_name[v['rule']] == nil then
return false, build_error_with_context(v['context'], "Rule " ..v['rule'].. " has 'enabled' key only, but no rule by that name already exists"), warnings
end
state.rules_by_name[v['rule']]['enabled'] = v['enabled']
end
else
-- Convert the priority-as-string to a priority-as-number now
v['priority_num'] = priorities[v['priority']]
if v['priority_num'] == nil then
@@ -659,6 +737,7 @@ function load_rules_doc(rules_mgr, doc, load_state)
else
state.skipped_rules_by_name[v['rule']] = v
end
end
end
else
local context = v['context']
@@ -769,11 +848,10 @@ end
-- Returns:
-- - Load Result: bool
-- - required engine version. will be nil when load result is false
-- - required_plugin_versions. will be nil when load_result is false
-- - List of Errors
-- - List of Warnings
function load_rules(sinsp_lua_parser,
json_lua_parser,
rules_content,
function load_rules(rules_content,
rules_mgr,
verbose,
all_events,
@@ -783,7 +861,7 @@ function load_rules(sinsp_lua_parser,
local warnings = {}
local load_state = {lines={}, indices={}, cur_item_idx=0, min_priority=min_priority, required_engine_version=0}
local load_state = {lines={}, indices={}, cur_item_idx=0, min_priority=min_priority, required_engine_version=0, required_plugin_versions={}}
load_state.lines, load_state.indices = split_lines(rules_content)
@@ -804,29 +882,29 @@ function load_rules(sinsp_lua_parser,
row = tonumber(row)
col = tonumber(col)
return false, nil, build_error(load_state.lines, row, 3, docs), warnings
return false, nil, nil, build_error(load_state.lines, row, 3, docs), warnings
end
if docs == nil then
-- An empty rules file is acceptable
return true, load_state.required_engine_version, {}, warnings
return true, load_state.required_engine_version, {}, {}, warnings
end
if type(docs) ~= "table" then
return false, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
return false, nil, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
end
for docidx, doc in ipairs(docs) do
if type(doc) ~= "table" then
return false, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
return false, nil, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
end
-- Look for non-numeric indices--implies that document is not array
-- of objects.
for key, val in pairs(doc) do
if type(key) ~= "number" then
return false, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml array of objects"), warnings
return false, nil, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml array of objects"), warnings
end
end
@@ -839,7 +917,7 @@ function load_rules(sinsp_lua_parser,
end
if not res then
return res, nil, errors, warnings
return res, nil, nil, errors, warnings
end
end
@@ -880,13 +958,7 @@ function load_rules(sinsp_lua_parser,
local status, ast = compiler.compile_macro(v['condition'], state.macros, state.lists)
if status == false then
return false, nil, build_error_with_context(v['context'], ast), warnings
end
if v['source'] == "syscall" then
if not all_events then
sinsp_rule_utils.check_for_ignored_syscalls_events(ast, 'macro', v['condition'])
end
return false, nil, nil, build_error_with_context(v['context'], ast), warnings
end
state.macros[v['macro']] = {["ast"] = ast.filter.value, ["used"] = false}
@@ -912,7 +984,7 @@ function load_rules(sinsp_lua_parser,
end
if err ~= nil then
return false, nil, build_error_with_context(v['context'], err), warnings
return false, nil, nil, build_error_with_context(v['context'], err), warnings
end
if icond ~= "" then
@@ -933,42 +1005,23 @@ function load_rules(sinsp_lua_parser,
warn_evttypes = v['warn_evttypes']
end
local status, filter_ast, filters = compiler.compile_filter(v['rule'], v['compile_condition'],
state.macros, state.lists)
local status, filter_ast = compiler.compile_filter(v['rule'], v['compile_condition'],
state.macros, state.lists)
if status == false then
return false, nil, build_error_with_context(v['context'], filter_ast), warnings
end
local evtttypes = {}
local syscallnums = {}
if v['source'] == "syscall" then
if not all_events then
sinsp_rule_utils.check_for_ignored_syscalls_events(filter_ast, 'rule', v['rule'])
end
evttypes, syscallnums = sinsp_rule_utils.get_evttypes_syscalls(name, filter_ast, v['compile_condition'], warn_evttypes, verbose)
end
-- If a filter in the rule doesn't exist, either skip the rule
-- or raise an error, depending on the value of
-- skip-if-unknown-filter.
for filter, _ in pairs(filters) do
if not is_defined_filter(filter) then
msg = "rule \""..v['rule'].."\": contains unknown filter "..filter
warnings[#warnings + 1] = msg
if not v['skip-if-unknown-filter'] then
return false, nil, build_error_with_context(v['context'], msg), warnings
else
print("Skipping "..msg)
goto next_rule
end
end
return false, nil, nil, build_error_with_context(v['context'], filter_ast), warnings
end
if (filter_ast.type == "Rule") then
valid = falco_rules.is_source_valid(rules_mgr, v['source'])
if valid == false then
msg = "Rule "..v['rule']..": warning (unknown-source): unknown source "..v['source']..", skipping"
warnings[#warnings + 1] = msg
goto next_rule
end
state.n_rules = state.n_rules + 1
state.rules_by_idx[state.n_rules] = v
@@ -983,15 +1036,30 @@ function load_rules(sinsp_lua_parser,
if (v['tags'] == nil) then
v['tags'] = {}
end
if v['source'] == "syscall" then
install_filter(filter_ast.filter.value, filter, sinsp_lua_parser)
-- Pass the filter and event types back up
falco_rules.add_filter(rules_mgr, v['rule'], evttypes, syscallnums, v['tags'])
elseif v['source'] == "k8s_audit" then
install_filter(filter_ast.filter.value, k8s_audit_filter, json_lua_parser)
lua_parser = falco_rules.create_lua_parser(rules_mgr, v['source'])
local err = create_filter_obj(filter_ast.filter.value, lua_parser)
if err ~= nil then
falco_rules.add_k8s_audit_filter(rules_mgr, v['rule'], v['tags'])
-- If a rule has a property skip-if-unknown-filter: true,
-- and the error is about an undefined field, print a
-- message but continue.
if v['skip-if-unknown-filter'] == true and string.find(err, "filter_check called with nonexistent field") ~= nil then
msg = "Rule "..v['rule']..": warning (unknown-field):"
warnings[#warnings + 1] = msg
else
msg = "Rule "..v['rule']..": error "..err
return false, nil, nil, build_error_with_context(v['context'], msg), warnings
end
else
num_evttypes = falco_rules.add_filter(rules_mgr, lua_parser, v['rule'], v['source'], v['tags'])
if num_evttypes == 0 or num_evttypes > 100 then
if warn_evttypes == true then
msg = "Rule "..v['rule']..": warning (no-evttype):"
warnings[#warnings + 1] = msg
end
end
end
-- Rule ASTs are merged together into one big AST, with "OR" between each
@@ -1042,14 +1110,12 @@ function load_rules(sinsp_lua_parser,
-- Ensure that the output field is properly formatted by
-- creating a formatter from it. Any error will be thrown
-- up to the top level.
local err, formatter = formats.formatter(v['source'], v['output'])
if err == nil then
formats.free_formatter(v['source'], formatter)
else
return false, nil, build_error_with_context(v['context'], err), warnings
local err = falco_rules.is_format_valid(rules_mgr, v['source'], v['output'])
if err ~= nil then
return false, nil, nil, build_error_with_context(v['context'], err), warnings
end
else
return false, nil, build_error_with_context(v['context'], "Unexpected type in load_rule: "..filter_ast.type), warnings
return false, nil, nil, build_error_with_context(v['context'], "Unexpected type in load_rule: "..filter_ast.type), warnings
end
::next_rule::
@@ -1072,7 +1138,7 @@ function load_rules(sinsp_lua_parser,
io.flush()
return true, load_state.required_engine_version, {}, warnings
return true, load_state.required_engine_version, load_state.required_plugin_versions, {}, warnings
end
local rule_fmt = "%-50s %s"
@@ -1156,7 +1222,7 @@ function on_event(rule_id)
error ("rule_loader.on_event(): could not find rule by name: ", rule.rule)
end
return rule.rule, rule.priority_num, output, combined_rule.exception_fields
return rule.rule, rule.priority_num, output, combined_rule.exception_fields, rule.tags
end
function print_stats()

View File

@@ -1,199 +0,0 @@
-- Copyright (C) 2019 The Falco Authors.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local parser = require("parser")
local sinsp_rule_utils = {}
function sinsp_rule_utils.check_for_ignored_syscalls_events(ast, filter_type, source)
function check_syscall(val)
if ignored_syscalls[val] then
error("Ignored syscall \""..val.."\" in "..filter_type..": "..source)
end
end
function check_event(val)
if ignored_events[val] then
error("Ignored event \""..val.."\" in "..filter_type..": "..source)
end
end
function cb(node)
if node.left.type == "FieldName" and
(node.left.value == "evt.type" or
node.left.value == "syscall.type") then
if (node.operator == "in" or
node.operator == "intersects" or
node.operator == "pmatch") then
for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then
if node.left.value == "evt.type" then
check_event(v.value)
else
check_syscall(v.value)
end
end
end
else
if node.right.type == "BareString" then
if node.left.value == "evt.type" then
check_event(node.right.value)
else
check_syscall(node.right.value)
end
end
end
end
end
parser.traverse_ast(ast, {BinaryRelOp=1}, cb)
end
-- Examine the ast and find the event types/syscalls for which the
-- rule should run. All evt.type references are added as event types
-- up until the first "!=" binary operator or unary not operator. If
-- no event type checks are found afterward in the rule, the rule is
-- considered optimized and is associated with the event type(s).
--
-- Otherwise, the rule is associated with a 'catchall' category and is
-- run for all event types/syscalls. (Also, a warning is printed).
--
function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes, verbose)
local evttypes = {}
local syscallnums = {}
local evtnames = {}
local found_event = false
local found_not = false
local found_event_after_not = false
function cb(node)
if node.type == "UnaryBoolOp" then
if node.operator == "not" then
found_not = true
end
else
if node.operator == "!=" then
found_not = true
end
if node.left.type == "FieldName" and node.left.value == "evt.type" then
found_event = true
if found_not then
found_event_after_not = true
end
if (node.operator == "in" or
node.operator == "intersects" or
node.operator == "pmatch") then
for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then
-- The event must be a known event
if events[v.value] == nil and syscalls[v.value] == nil then
error("Unknown event/syscall \""..v.value.."\" in filter: "..source)
end
evtnames[v.value] = 1
if events[v.value] ~= nil then
for id in string.gmatch(events[v.value], "%S+") do
evttypes[id] = 1
end
end
if syscalls[v.value] ~= nil then
for id in string.gmatch(syscalls[v.value], "%S+") do
syscallnums[id] = 1
end
end
end
end
else
if node.right.type == "BareString" then
-- The event must be a known event
if events[node.right.value] == nil and syscalls[node.right.value] == nil then
error("Unknown event/syscall \""..node.right.value.."\" in filter: "..source)
end
evtnames[node.right.value] = 1
if events[node.right.value] ~= nil then
for id in string.gmatch(events[node.right.value], "%S+") do
evttypes[id] = 1
end
end
if syscalls[node.right.value] ~= nil then
for id in string.gmatch(syscalls[node.right.value], "%S+") do
syscallnums[id] = 1
end
end
end
end
end
end
end
parser.traverse_ast(ast.filter.value, {BinaryRelOp=1, UnaryBoolOp=1} , cb)
if not found_event then
if warn_evttypes == true then
io.stderr:write("Rule "..name..": warning (no-evttype):\n")
io.stderr:write(source.."\n")
io.stderr:write(" did not contain any evt.type restriction, meaning it will run for all event types.\n")
io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n")
end
evttypes = {}
syscallnums = {}
evtnames = {}
end
if found_event_after_not then
if warn_evttypes == true then
io.stderr:write("Rule "..name..": warning (trailing-evttype):\n")
io.stderr:write(source.."\n")
io.stderr:write(" does not have all evt.type restrictions at the beginning of the condition,\n")
io.stderr:write(" or uses a negative match (i.e. \"not\"/\"!=\") for some evt.type restriction.\n")
io.stderr:write(" This has a performance penalty, as the rule can not be limited to specific event types.\n")
io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
io.stderr:write(" replacing negative matches with positive matches if possible.\n")
end
evttypes = {}
syscallnums = {}
evtnames = {}
end
evtnames_only = {}
local num_evtnames = 0
for name, dummy in pairs(evtnames) do
table.insert(evtnames_only, name)
num_evtnames = num_evtnames + 1
end
if num_evtnames == 0 then
table.insert(evtnames_only, "all")
end
table.sort(evtnames_only)
if verbose then
io.stderr:write("Event types/Syscalls for rule "..name..": "..table.concat(evtnames_only, ",").."\n")
end
return evttypes, syscallnums
end
return sinsp_rule_utils

View File

@@ -30,26 +30,32 @@ extern "C" {
const static struct luaL_Reg ll_falco_rules[] =
{
{"clear_filters", &falco_rules::clear_filters},
{"create_lua_parser", &falco_rules::create_lua_parser},
{"add_filter", &falco_rules::add_filter},
{"add_k8s_audit_filter", &falco_rules::add_k8s_audit_filter},
{"enable_rule", &falco_rules::enable_rule},
{"engine_version", &falco_rules::engine_version},
{"is_source_valid", &falco_rules::is_source_valid},
{"is_format_valid", &falco_rules::is_format_valid},
{"is_defined_field", &falco_rules::is_defined_field},
{NULL, NULL}};
falco_rules::falco_rules(sinsp* inspector,
falco_engine *engine,
falco_rules::falco_rules(falco_engine *engine,
lua_State *ls)
: m_inspector(inspector),
m_engine(engine),
: m_engine(engine),
m_ls(ls)
{
m_sinsp_lua_parser = new lua_parser(engine->sinsp_factory(), m_ls, "filter");
m_json_lua_parser = new lua_parser(engine->json_factory(), m_ls, "k8s_audit_filter");
}
void falco_rules::add_filter_factory(const std::string &source,
std::shared_ptr<gen_event_filter_factory> factory)
{
m_filter_factories[source] = factory;
}
void falco_rules::init(lua_State *ls)
{
luaL_openlib(ls, "falco_rules", ll_falco_rules, 0);
lua_parser::register_callbacks(ls, "filter");
}
int falco_rules::clear_filters(lua_State *ls)
@@ -71,12 +77,51 @@ void falco_rules::clear_filters()
m_engine->clear_filters();
}
int falco_rules::create_lua_parser(lua_State *ls)
{
if (! lua_islightuserdata(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid argument passed to create_lua_parser()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
std::string source = lua_tostring(ls, -1);
std::string errstr;
lua_parser *lp = rules->create_lua_parser(source, errstr);
if(lp == NULL) {
lua_pushstring(ls, errstr.c_str());
lua_error(ls);
}
lua_pushlightuserdata(ls, lp);
return 1;
}
lua_parser *falco_rules::create_lua_parser(std::string &source, std::string &errstr)
{
auto it = m_filter_factories.find(source);
if(it == m_filter_factories.end())
{
errstr = string("Unknown event source ") + source;
return NULL;
}
lua_parser *lp = new lua_parser(it->second);
return lp;
}
int falco_rules::add_filter(lua_State *ls)
{
if (! lua_islightuserdata(ls, -5) ||
! lua_isstring(ls, -4) ||
! lua_istable(ls, -3) ||
! lua_istable(ls, -2) ||
! lua_islightuserdata(ls, -4) ||
! lua_isstring(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_istable(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to add_filter()");
@@ -84,31 +129,9 @@ int falco_rules::add_filter(lua_State *ls)
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -5);
const char *rulec = lua_tostring(ls, -4);
set<uint32_t> evttypes;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -4) != 0) {
// key is at index -2, value is at index
// -1. We want the keys.
evttypes.insert(luaL_checknumber(ls, -2));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
set<uint32_t> syscalls;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -3) != 0) {
// key is at index -2, value is at index
// -1. We want the keys.
syscalls.insert(luaL_checknumber(ls, -2));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
lua_parser *lp = (lua_parser *) lua_topointer(ls, -4);
std::string rule = lua_tostring(ls, -3);
std::string source = lua_tostring(ls, -2);
set<string> tags;
@@ -122,61 +145,25 @@ int falco_rules::add_filter(lua_State *ls)
lua_pop(ls, 1);
}
std::string rule = rulec;
rules->add_filter(rule, evttypes, syscalls, tags);
size_t num_evttypes = lp->filter()->evttypes().size();
return 0;
}
int falco_rules::add_k8s_audit_filter(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_istable(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to add_k8s_audit_filter()");
try {
rules->add_filter(lp->filter(), rule, source, tags);
} catch (exception &e) {
std::string errstr = string("Could not add rule to falco engine: ") + e.what();
lua_pushstring(ls, errstr.c_str());
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
const char *rulec = lua_tostring(ls, -2);
delete lp;
set<string> tags;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -2) != 0) {
// key is at index -2, value is at index
// -1. We want the values.
tags.insert(lua_tostring(ls, -1));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
std::string rule = rulec;
rules->add_k8s_audit_filter(rule, tags);
return 0;
lua_pushnumber(ls, num_evttypes);
return 1;
}
void falco_rules::add_filter(string &rule, set<uint32_t> &evttypes, set<uint32_t> &syscalls, set<string> &tags)
void falco_rules::add_filter(std::shared_ptr<gen_event_filter> filter, string &rule, string &source, set<string> &tags)
{
// 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.
sinsp_filter *filter = (sinsp_filter *) m_sinsp_lua_parser->get_filter(true);
m_engine->add_sinsp_filter(rule, evttypes, syscalls, tags, filter);
}
void falco_rules::add_k8s_audit_filter(string &rule, set<string> &tags)
{
// 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.
json_event_filter *filter = (json_event_filter *) m_json_lua_parser->get_filter(true);
m_engine->add_k8s_audit_filter(rule, tags, filter);
m_engine->add_filter(filter, rule, source, tags);
}
int falco_rules::enable_rule(lua_State *ls)
@@ -219,6 +206,128 @@ int falco_rules::engine_version(lua_State *ls)
return 1;
}
bool falco_rules::is_source_valid(const std::string &source)
{
return m_engine->is_source_valid(source);
}
int falco_rules::is_source_valid(lua_State *ls)
{
if (! lua_islightuserdata(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_source_valid");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
string source = luaL_checkstring(ls, -1);
bool ret = rules->is_source_valid(source);
lua_pushboolean(ls, (ret ? 1 : 0));
return 1;
}
int falco_rules::is_format_valid(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_format_valid");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
string source = luaL_checkstring(ls, -2);
string format = luaL_checkstring(ls, -1);
string errstr;
bool ret = rules->is_format_valid(source, format, errstr);
if (!ret)
{
lua_pushstring(ls, errstr.c_str());
}
else
{
lua_pushnil(ls);
}
return 1;
}
bool falco_rules::is_format_valid(const std::string &source, const std::string &format, std::string &errstr)
{
bool ret = true;
try
{
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_engine->create_formatter(source, format);
}
catch(exception &e)
{
std::ostringstream os;
os << "Invalid output format '"
<< format
<< "': '"
<< e.what()
<< "'";
errstr = os.str();
ret = false;
}
return ret;
}
int falco_rules::is_defined_field(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_defined_field");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
string source = luaL_checkstring(ls, -2);
string fldname = luaL_checkstring(ls, -1);
bool ret = rules->is_defined_field(source, fldname);
lua_pushboolean(ls, (ret ? 1 : 0));
return 1;
}
bool falco_rules::is_defined_field(const std::string &source, const std::string &fldname)
{
auto it = m_filter_factories.find(source);
if(it == m_filter_factories.end())
{
return false;
}
auto *chk = it->second->new_filtercheck(fldname.c_str());
if (chk == NULL)
{
return false;
}
delete(chk);
return true;
}
static std::list<std::string> get_lua_table_values(lua_State *ls, int idx)
{
std::list<std::string> ret;
@@ -244,204 +353,52 @@ static std::list<std::string> get_lua_table_values(lua_State *ls, int idx)
return ret;
}
static void get_lua_table_list_values(lua_State *ls,
int idx,
std::map<std::string, std::list<std::string>> &required_plugin_versions)
{
if (lua_isnil(ls, idx)) {
return;
}
lua_pushnil(ls); /* first key */
while (lua_next(ls, idx-1) != 0) {
// key is at index -2, table of values is at index -1.
if (! lua_isstring(ls, -2)) {
std::string err = "Non-string key in table of strings";
throw falco_exception(err);
}
std::string key = string(lua_tostring(ls, -2));
std::list<std::string> vals = get_lua_table_values(ls, -1);
if (required_plugin_versions.find(key) == required_plugin_versions.end())
{
required_plugin_versions[key] = vals;
}
else
{
required_plugin_versions[key].insert(required_plugin_versions[key].end(),
vals.begin(),
vals.end());
}
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
}
void falco_rules::load_rules(const string &rules_content,
bool verbose, bool all_events,
string &extra, bool replace_container_info,
falco_common::priority_type min_priority,
uint64_t &required_engine_version)
uint64_t &required_engine_version,
std::map<std::string, std::list<std::string>> &required_plugin_versions)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
{
// Create a table containing all events, so they can
// be mapped to event ids.
sinsp_evttables* einfo = m_inspector->get_event_info_tables();
const struct ppm_event_info* etable = einfo->m_event_info;
const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table;
map<string,string> events_by_name;
for(uint32_t j = 0; j < PPM_EVENT_MAX; j++)
{
auto it = events_by_name.find(etable[j].name);
if (it == events_by_name.end()) {
events_by_name[etable[j].name] = to_string(j);
} else {
string cur = it->second;
cur += " ";
cur += to_string(j);
events_by_name[etable[j].name] = cur;
}
}
lua_newtable(m_ls);
for( auto kv : events_by_name)
{
lua_pushstring(m_ls, kv.first.c_str());
lua_pushstring(m_ls, kv.second.c_str());
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_events.c_str());
map<string,string> syscalls_by_name;
for(uint32_t j = 0; j < PPM_SC_MAX; j++)
{
auto it = syscalls_by_name.find(stable[j].name);
if (it == syscalls_by_name.end())
{
syscalls_by_name[stable[j].name] = to_string(j);
}
else
{
string cur = it->second;
cur += " ";
cur += to_string(j);
syscalls_by_name[stable[j].name] = cur;
}
}
lua_newtable(m_ls);
for( auto kv : syscalls_by_name)
{
lua_pushstring(m_ls, kv.first.c_str());
lua_pushstring(m_ls, kv.second.c_str());
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_syscalls.c_str());
// Create a table containing the syscalls/events that
// are ignored by the kernel module. load_rules will
// return an error if any rule references one of these
// syscalls/events.
lua_newtable(m_ls);
for(uint32_t j = 0; j < PPM_EVENT_MAX; j++)
{
if(etable[j].flags & EF_DROP_SIMPLE_CONS)
{
lua_pushstring(m_ls, etable[j].name);
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
}
lua_setglobal(m_ls, m_lua_ignored_events.c_str());
lua_newtable(m_ls);
for(uint32_t j = 0; j < PPM_SC_MAX; j++)
{
if(stable[j].flags & EF_DROP_SIMPLE_CONS)
{
lua_pushstring(m_ls, stable[j].name);
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
}
lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str());
vector<const filter_check_info*> fc_plugins;
sinsp::get_filtercheck_fields_info(&fc_plugins);
set<string> no_argument_filters;
set<string> argument_filters;
for(uint32_t j = 0; j < fc_plugins.size(); j++)
{
const filter_check_info* fci = fc_plugins[j];
if(fci->m_flags & filter_check_info::FL_HIDDEN)
{
continue;
}
for(int32_t k = 0; k < fci->m_nfields; k++)
{
const filtercheck_field_info* fld = &fci->m_fields[k];
if(fld->m_flags & EPF_TABLE_ONLY ||
fld->m_flags & EPF_PRINT_ONLY)
{
continue;
}
// Some filters can work with or without an argument
std::set<string> flexible_filters = {
"proc.aname",
"proc.apid"
};
if(fld->m_flags & EPF_REQUIRES_ARGUMENT ||
flexible_filters.find(fld->m_name) != flexible_filters.end())
{
argument_filters.insert(fld->m_name);
}
if(!(fld->m_flags & EPF_REQUIRES_ARGUMENT) ||
flexible_filters.find(fld->m_name) != flexible_filters.end())
{
no_argument_filters.insert(fld->m_name);
}
}
}
for(auto &chk_field : m_engine->json_factory().get_fields())
{
for(auto &field : chk_field.m_fields)
{
switch(field.m_idx_mode)
{
case json_event_filter_check::IDX_REQUIRED:
argument_filters.insert(field.m_name);
break;
case json_event_filter_check::IDX_ALLOWED:
argument_filters.insert(field.m_name);
no_argument_filters.insert(field.m_name);
break;
case json_event_filter_check::IDX_NONE:
no_argument_filters.insert(field.m_name);
break;
default:
break;
}
}
}
// Create tables containing all filtercheck
// names. They are split into names that require
// arguments and ones that do not.
lua_newtable(m_ls);
for(auto &field : argument_filters)
{
lua_pushstring(m_ls, field.c_str());
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_defined_arg_filters.c_str());
lua_newtable(m_ls);
for(auto &field : no_argument_filters)
{
lua_pushstring(m_ls, field.c_str());
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_defined_noarg_filters.c_str());
lua_pushlightuserdata(m_ls, m_sinsp_lua_parser);
lua_pushlightuserdata(m_ls, m_json_lua_parser);
lua_pushstring(m_ls, rules_content.c_str());
lua_pushlightuserdata(m_ls, this);
lua_pushboolean(m_ls, (verbose ? 1 : 0));
@@ -449,7 +406,7 @@ void falco_rules::load_rules(const string &rules_content,
lua_pushstring(m_ls, extra.c_str());
lua_pushboolean(m_ls, (replace_container_info ? 1 : 0));
lua_pushnumber(m_ls, min_priority);
if(lua_pcall(m_ls, 9, 4, 0) != 0)
if(lua_pcall(m_ls, 7, 5, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
@@ -461,10 +418,12 @@ void falco_rules::load_rules(const string &rules_content,
// Returns:
// Load result: bool
// required engine version: will be nil when load result is false
// required_plugin_versions: will be nil when load result is false
// array of errors
// array of warnings
bool successful = lua_toboolean(m_ls, -4);
required_engine_version = lua_tonumber(m_ls, -3);
bool successful = lua_toboolean(m_ls, -5);
required_engine_version = lua_tonumber(m_ls, -4);
get_lua_table_list_values(m_ls, -3, required_plugin_versions);
std::list<std::string> errors = get_lua_table_values(m_ls, -2);
std::list<std::string> warnings = get_lua_table_values(m_ls, -1);
@@ -533,6 +492,4 @@ void falco_rules::describe_rule(std::string *rule)
falco_rules::~falco_rules()
{
delete m_sinsp_lua_parser;
delete m_json_lua_parser;
}

View File

@@ -32,41 +32,55 @@ class falco_engine;
class falco_rules
{
public:
falco_rules(sinsp* inspector,
falco_engine *engine,
falco_rules(falco_engine *engine,
lua_State *ls);
~falco_rules();
void add_filter_factory(const std::string &source,
std::shared_ptr<gen_event_filter_factory> factory);
void load_rules(const string &rules_content, bool verbose, bool all_events,
std::string &extra, bool replace_container_info,
falco_common::priority_type min_priority,
uint64_t &required_engine_version);
uint64_t &required_engine_version,
std::map<std::string, std::list<std::string>> &required_plugin_versions);
void describe_rule(string *rule);
bool is_source_valid(const std::string &source);
bool is_format_valid(const std::string &source, const std::string &format, std::string &errstr);
bool is_defined_field(const std::string &source, const std::string &field);
static void init(lua_State *ls);
static int clear_filters(lua_State *ls);
static int create_lua_parser(lua_State *ls);
static int add_filter(lua_State *ls);
static int add_k8s_audit_filter(lua_State *ls);
static int enable_rule(lua_State *ls);
static int engine_version(lua_State *ls);
static int is_source_valid(lua_State *ls);
// err = falco_rules.is_format_valid(source, format_string)
static int is_format_valid(lua_State *ls);
// err = falco_rules.is_defined_field(source, field)
static int is_defined_field(lua_State *ls);
private:
void clear_filters();
void add_filter(string &rule, std::set<uint32_t> &evttypes, std::set<uint32_t> &syscalls, std::set<string> &tags);
void add_k8s_audit_filter(string &rule, std::set<string> &tags);
// XXX/mstemm can I make this a shared_ptr?
lua_parser * create_lua_parser(std::string &source, std::string &errstr);
void add_filter(std::shared_ptr<gen_event_filter> filter, string &rule, string &source, std::set<string> &tags);
void enable_rule(string &rule, bool enabled);
lua_parser* m_sinsp_lua_parser;
lua_parser* m_json_lua_parser;
sinsp* m_inspector;
falco_engine *m_engine;
lua_State* m_ls;
// Maps from event source to an object that can create rules
// for that event source.
std::map<std::string, std::shared_ptr<gen_event_filter_factory>> m_filter_factories;
string m_lua_load_rules = "load_rules";
string m_lua_ignored_syscalls = "ignored_syscalls";
string m_lua_ignored_events = "ignored_events";
string m_lua_defined_arg_filters = "defined_arg_filters";
string m_lua_defined_noarg_filters = "defined_noarg_filters";
string m_lua_events = "events";
string m_lua_syscalls = "syscalls";
string m_lua_describe_rule = "describe_rule";
};

View File

@@ -17,6 +17,8 @@ limitations under the License.
#include "ruleset.h"
#include "banned.h" // This raises a compilation error when certain functions are used
#include <algorithm>
using namespace std;
falco_ruleset::falco_ruleset()
@@ -25,127 +27,112 @@ falco_ruleset::falco_ruleset()
falco_ruleset::~falco_ruleset()
{
for(const auto &val : m_filters)
{
delete val.second->filter;
delete val.second;
}
for(auto &ruleset : m_rulesets)
{
delete ruleset;
}
m_filters.clear();
}
falco_ruleset::ruleset_filters::ruleset_filters():
m_num_filters(0)
falco_ruleset::ruleset_filters::ruleset_filters()
{
}
falco_ruleset::ruleset_filters::~ruleset_filters()
{
for(uint32_t i = 0; i < m_filter_by_event_tag.size(); i++)
}
void falco_ruleset::ruleset_filters::add_wrapper_to_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap)
{
// This is O(n) but it's also uncommon
// (when loading rules only).
auto pos = std::find(wrappers.begin(),
wrappers.end(),
wrap);
if(pos == wrappers.end())
{
if(m_filter_by_event_tag[i])
{
delete m_filter_by_event_tag[i];
m_filter_by_event_tag[i] = NULL;
}
wrappers.push_back(wrap);
}
}
void falco_ruleset::ruleset_filters::add_filter(filter_wrapper *wrap)
void falco_ruleset::ruleset_filters::remove_wrapper_from_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap)
{
bool added = false;
for(uint32_t etag = 0; etag < wrap->event_tags.size(); etag++)
// This is O(n) but it's also uncommon
// (when loading rules only).
auto pos = std::find(wrappers.begin(),
wrappers.end(),
wrap);
if(pos != wrappers.end())
{
if(wrap->event_tags[etag])
{
added = true;
if(m_filter_by_event_tag.size() <= etag)
{
m_filter_by_event_tag.resize(etag + 1);
}
if(!m_filter_by_event_tag[etag])
{
m_filter_by_event_tag[etag] = new list<filter_wrapper *>();
}
m_filter_by_event_tag[etag]->push_back(wrap);
}
}
if(added)
{
m_num_filters++;
wrappers.erase(pos);
}
}
void falco_ruleset::ruleset_filters::remove_filter(filter_wrapper *wrap)
void falco_ruleset::ruleset_filters::add_filter(std::shared_ptr<filter_wrapper> wrap)
{
bool removed = false;
std::set<uint16_t> fevttypes = wrap->filter->evttypes();
for(uint32_t etag = 0; etag < wrap->event_tags.size(); etag++)
if(fevttypes.empty())
{
if(wrap->event_tags[etag])
// Should run for all event types
add_wrapper_to_list(m_filter_all_event_types, wrap);
}
else
{
for(auto &etype : fevttypes)
{
if(etag < m_filter_by_event_tag.size())
if(m_filter_by_event_type.size() <= etype)
{
list<filter_wrapper *> *l = m_filter_by_event_tag[etag];
if(l)
{
auto it = remove(l->begin(),
l->end(),
wrap);
m_filter_by_event_type.resize(etype + 1);
}
if(it != l->end())
{
removed = true;
add_wrapper_to_list(m_filter_by_event_type[etype], wrap);
}
}
l->erase(it,
l->end());
m_filters.insert(wrap);
}
if(l->size() == 0)
{
delete l;
m_filter_by_event_tag[etag] = NULL;
}
}
}
void falco_ruleset::ruleset_filters::remove_filter(std::shared_ptr<filter_wrapper> wrap)
{
std::set<uint16_t> fevttypes = wrap->filter->evttypes();
if(fevttypes.empty())
{
remove_wrapper_from_list(m_filter_all_event_types, wrap);
}
else
{
for(auto &etype : fevttypes)
{
if( etype < m_filter_by_event_type.size() )
{
remove_wrapper_from_list(m_filter_by_event_type[etype], wrap);
}
}
}
if(removed)
{
m_num_filters--;
}
m_filters.erase(wrap);
}
uint64_t falco_ruleset::ruleset_filters::num_filters()
{
return m_num_filters;
return m_filters.size();
}
bool falco_ruleset::ruleset_filters::run(gen_event *evt, uint32_t etag)
bool falco_ruleset::ruleset_filters::run(gen_event *evt)
{
if(etag >= m_filter_by_event_tag.size())
if(evt->get_type() >= m_filter_by_event_type.size())
{
return false;
}
list<filter_wrapper *> *filters = m_filter_by_event_tag[etag];
if(!filters)
for(auto &wrap : m_filter_by_event_type[evt->get_type()])
{
return false;
if(wrap->filter->run(evt))
{
return true;
}
}
for(auto &wrap : *filters)
// Finally, try filters that are not specific to an event type.
for(auto &wrap : m_filter_all_event_types)
{
if(wrap->filter->run(evt))
{
@@ -156,83 +143,61 @@ bool falco_ruleset::ruleset_filters::run(gen_event *evt, uint32_t etag)
return false;
}
void falco_ruleset::ruleset_filters::event_tags_for_ruleset(vector<bool> &event_tags)
void falco_ruleset::ruleset_filters::evttypes_for_ruleset(std::set<uint16_t> &evttypes)
{
event_tags.assign(m_filter_by_event_tag.size(), false);
evttypes.clear();
for(uint32_t etag = 0; etag < m_filter_by_event_tag.size(); etag++)
for(auto &wrap : m_filters)
{
list<filter_wrapper *> *filters = m_filter_by_event_tag[etag];
if(filters)
{
event_tags[etag] = true;
}
auto fevttypes = wrap->filter->evttypes();
evttypes.insert(fevttypes.begin(), fevttypes.end());
}
}
void falco_ruleset::add(string &name,
set<string> &tags,
set<uint32_t> &event_tags,
gen_event_filter *filter)
std::shared_ptr<gen_event_filter> filter)
{
filter_wrapper *wrap = new filter_wrapper();
std::shared_ptr<filter_wrapper> wrap(new filter_wrapper());
wrap->name = name;
wrap->tags = tags;
wrap->filter = filter;
for(auto &etag : event_tags)
{
wrap->event_tags.resize(etag + 1);
wrap->event_tags[etag] = true;
}
m_filters.insert(pair<string, filter_wrapper *>(name, wrap));
for(const auto &tag : tags)
{
auto it = m_filter_by_event_tag.lower_bound(tag);
if(it == m_filter_by_event_tag.end() ||
it->first != tag)
{
it = m_filter_by_event_tag.emplace_hint(it,
make_pair(tag, list<filter_wrapper *>()));
}
it->second.push_back(wrap);
}
m_filters.insert(wrap);
}
void falco_ruleset::enable(const string &substring, bool match_exact, bool enabled, uint16_t ruleset)
{
while(m_rulesets.size() < (size_t)ruleset + 1)
{
m_rulesets.push_back(new ruleset_filters());
m_rulesets.emplace_back(new ruleset_filters());
}
for(const auto &val : m_filters)
for(const auto &wrap : m_filters)
{
bool matches;
if(match_exact)
{
size_t pos = val.first.find(substring);
size_t pos = wrap->name.find(substring);
matches = (substring == "" || (pos == 0 &&
substring.size() == val.first.size()));
substring.size() == wrap->name.size()));
}
else
{
matches = (substring == "" || (val.first.find(substring) != string::npos));
matches = (substring == "" || (wrap->name.find(substring) != string::npos));
}
if(matches)
{
if(enabled)
{
m_rulesets[ruleset]->add_filter(val.second);
m_rulesets[ruleset]->add_filter(wrap);
}
else
{
m_rulesets[ruleset]->remove_filter(val.second);
m_rulesets[ruleset]->remove_filter(wrap);
}
}
}
@@ -242,12 +207,18 @@ void falco_ruleset::enable_tags(const set<string> &tags, bool enabled, uint16_t
{
while(m_rulesets.size() < (size_t)ruleset + 1)
{
m_rulesets.push_back(new ruleset_filters());
m_rulesets.emplace_back(new ruleset_filters());
}
for(const auto &tag : tags)
for(const auto &wrap : m_filters)
{
for(const auto &wrap : m_filter_by_event_tag[tag])
std::set<string> intersect;
set_intersection(tags.begin(), tags.end(),
wrap->tags.begin(), wrap->tags.end(),
inserter(intersect, intersect.begin()));
if(!intersect.empty())
{
if(enabled)
{
@@ -265,141 +236,28 @@ uint64_t falco_ruleset::num_rules_for_ruleset(uint16_t ruleset)
{
while(m_rulesets.size() < (size_t)ruleset + 1)
{
m_rulesets.push_back(new ruleset_filters());
m_rulesets.emplace_back(new ruleset_filters());
}
return m_rulesets[ruleset]->num_filters();
}
bool falco_ruleset::run(gen_event *evt, uint32_t etag, uint16_t ruleset)
bool falco_ruleset::run(gen_event *evt, uint16_t ruleset)
{
if(m_rulesets.size() < (size_t)ruleset + 1)
{
return false;
}
return m_rulesets[ruleset]->run(evt, etag);
return m_rulesets[ruleset]->run(evt);
}
void falco_ruleset::event_tags_for_ruleset(vector<bool> &evttypes, uint16_t ruleset)
void falco_ruleset::evttypes_for_ruleset(set<uint16_t> &evttypes, uint16_t ruleset)
{
if(m_rulesets.size() < (size_t)ruleset + 1)
{
return;
}
return m_rulesets[ruleset]->event_tags_for_ruleset(evttypes);
}
falco_sinsp_ruleset::falco_sinsp_ruleset()
{
}
falco_sinsp_ruleset::~falco_sinsp_ruleset()
{
}
void falco_sinsp_ruleset::add(string &name,
set<uint32_t> &evttypes,
set<uint32_t> &syscalls,
set<string> &tags,
sinsp_filter *filter)
{
set<uint32_t> event_tags;
if(evttypes.size() + syscalls.size() == 0)
{
// If no evttypes or syscalls are specified, the filter is
// enabled for all evttypes/syscalls.
for(uint32_t i = 0; i < PPM_EVENT_MAX; i++)
{
evttypes.insert(i);
}
for(uint32_t i = 0; i < PPM_SC_MAX; i++)
{
syscalls.insert(i);
}
}
for(auto evttype : evttypes)
{
event_tags.insert(evttype_to_event_tag(evttype));
}
for(auto syscallid : syscalls)
{
event_tags.insert(syscall_to_event_tag(syscallid));
}
falco_ruleset::add(name, tags, event_tags, (gen_event_filter *)filter);
}
bool falco_sinsp_ruleset::run(sinsp_evt *evt, uint16_t ruleset)
{
uint32_t etag;
uint16_t etype = evt->get_type();
if(etype == PPME_GENERIC_E || etype == PPME_GENERIC_X)
{
sinsp_evt_param *parinfo = evt->get_param(0);
uint16_t syscallid = *(uint16_t *)parinfo->m_val;
etag = syscall_to_event_tag(syscallid);
}
else
{
etag = evttype_to_event_tag(etype);
}
return falco_ruleset::run((gen_event *)evt, etag, ruleset);
}
void falco_sinsp_ruleset::evttypes_for_ruleset(vector<bool> &evttypes, uint16_t ruleset)
{
vector<bool> event_tags;
event_tags_for_ruleset(event_tags, ruleset);
evttypes.assign(PPM_EVENT_MAX + 1, false);
for(uint32_t etype = 0; etype < PPM_EVENT_MAX; etype++)
{
uint32_t etag = evttype_to_event_tag(etype);
if(etag < event_tags.size() && event_tags[etag])
{
evttypes[etype] = true;
}
}
}
void falco_sinsp_ruleset::syscalls_for_ruleset(vector<bool> &syscalls, uint16_t ruleset)
{
vector<bool> event_tags;
event_tags_for_ruleset(event_tags, ruleset);
syscalls.assign(PPM_EVENT_MAX + 1, false);
for(uint32_t syscallid = 0; syscallid < PPM_SC_MAX; syscallid++)
{
uint32_t etag = evttype_to_event_tag(syscallid);
if(etag < event_tags.size() && event_tags[etag])
{
syscalls[syscallid] = true;
}
}
}
uint32_t falco_sinsp_ruleset::evttype_to_event_tag(uint32_t evttype)
{
return evttype;
}
uint32_t falco_sinsp_ruleset::syscall_to_event_tag(uint32_t syscallid)
{
return PPM_EVENT_MAX + 1 + syscallid;
return m_rulesets[ruleset]->evttypes_for_ruleset(evttypes);
}

View File

@@ -36,8 +36,7 @@ public:
void add(std::string &name,
std::set<std::string> &tags,
std::set<uint32_t> &event_tags,
gen_event_filter* filter);
std::shared_ptr<gen_event_filter> filter);
// rulesets are arbitrary numbers and should be managed by the caller.
// Note that rulesets are used to index into a std::vector so
@@ -65,23 +64,22 @@ public:
uint64_t num_rules_for_ruleset(uint16_t ruleset = 0);
// Match all filters against the provided event.
bool run(gen_event *evt, uint32_t etag, uint16_t ruleset = 0);
bool run(gen_event *evt, uint16_t ruleset = 0);
// Populate the provided vector, indexed by event tag, of the
// event tags associated with the given ruleset id. For
// example, event_tags[10] = true would mean that this ruleset
// relates to event tag 10.
void event_tags_for_ruleset(std::vector<bool> &event_tags, uint16_t ruleset);
// Populate the provided set of event types used by this ruleset.
void evttypes_for_ruleset(std::set<uint16_t> &evttypes, uint16_t ruleset);
private:
struct filter_wrapper {
gen_event_filter *filter;
// Indexes from event tag to enabled/disabled.
std::vector<bool> event_tags;
class filter_wrapper {
public:
std::string name;
std::set<std::string> tags;
std::shared_ptr<gen_event_filter> filter;
};
typedef std::list<std::shared_ptr<filter_wrapper>> filter_wrapper_list;
// A group of filters all having the same ruleset
class ruleset_filters {
public:
@@ -89,63 +87,33 @@ private:
virtual ~ruleset_filters();
void add_filter(filter_wrapper *wrap);
void remove_filter(filter_wrapper *wrap);
void add_filter(std::shared_ptr<filter_wrapper> wrap);
void remove_filter(std::shared_ptr<filter_wrapper> wrap);
uint64_t num_filters();
bool run(gen_event *evt, uint32_t etag);
bool run(gen_event *evt);
void event_tags_for_ruleset(std::vector<bool> &event_tags);
void evttypes_for_ruleset(std::set<uint16_t> &evttypes);
private:
uint64_t m_num_filters;
void add_wrapper_to_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap);
void remove_wrapper_from_list(filter_wrapper_list &wrappers, std::shared_ptr<filter_wrapper> wrap);
// Maps from event tag to a list of filters. There can
// be multiple filters for a given event tag.
std::vector<std::list<filter_wrapper *> *> m_filter_by_event_tag;
// Vector indexes from event type to a set of filters. There can
// be multiple filters for a given event type.
// NOTE: This is used only when the event sub-type is 0.
std::vector<filter_wrapper_list> m_filter_by_event_type;
filter_wrapper_list m_filter_all_event_types;
// All filters added. Used to make num_filters() fast.
std::set<std::shared_ptr<filter_wrapper>> m_filters;
};
std::vector<ruleset_filters *> m_rulesets;
// Vector indexes from ruleset id to set of rules.
std::vector<std::shared_ptr<ruleset_filters>> m_rulesets;
// Maps from tag to list of filters having that tag.
std::map<std::string, std::list<filter_wrapper *>> m_filter_by_event_tag;
// This holds all the filters passed to add(), so they can
// be cleaned up.
std::map<std::string,filter_wrapper *> m_filters;
};
// falco_sinsp_ruleset is a specialization of falco_ruleset that
// maps sinsp evttypes/syscalls to event tags.
class falco_sinsp_ruleset : public falco_ruleset
{
public:
falco_sinsp_ruleset();
virtual ~falco_sinsp_ruleset();
void add(std::string &name,
std::set<uint32_t> &evttypes,
std::set<uint32_t> &syscalls,
std::set<std::string> &tags,
sinsp_filter* filter);
bool run(sinsp_evt *evt, uint16_t ruleset = 0);
// Populate the provided vector, indexed by event type, of the
// event types associated with the given ruleset id. For
// example, evttypes[10] = true would mean that this ruleset
// relates to event type 10.
void evttypes_for_ruleset(std::vector<bool> &evttypes, uint16_t ruleset);
// Populate the provided vector, indexed by syscall code, of the
// syscall codes associated with the given ruleset id. For
// example, syscalls[10] = true would mean that this ruleset
// relates to syscall code 10.
void syscalls_for_ruleset(std::vector<bool> &syscalls, uint16_t ruleset);
private:
uint32_t evttype_to_event_tag(uint32_t evttype);
uint32_t syscall_to_event_tag(uint32_t syscallid);
// All filters added. The set of enabled filters is held in m_rulesets
std::set<std::shared_ptr<filter_wrapper>> m_filters;
};

View File

@@ -1,89 +0,0 @@
/*
Copyright (C) 2019 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <cstddef>
#include <functional>
#include <sys/time.h>
#include "token_bucket.h"
#include "utils.h"
#include "banned.h" // This raises a compilation error when certain functions are used
token_bucket::token_bucket():
token_bucket(sinsp_utils::get_current_time_ns)
{
}
token_bucket::token_bucket(std::function<uint64_t()> timer)
{
m_timer = timer;
init(1, 1);
}
token_bucket::~token_bucket()
{
}
void token_bucket::init(double rate, double max_tokens, uint64_t now)
{
m_rate = rate;
m_max_tokens = max_tokens;
m_tokens = max_tokens;
m_last_seen = now == 0 ? m_timer() : now;
}
bool token_bucket::claim()
{
return claim(1, m_timer());
}
bool token_bucket::claim(double tokens, uint64_t now)
{
double tokens_gained = m_rate * ((now - m_last_seen) / (1000000000.0));
m_last_seen = now;
m_tokens += tokens_gained;
//
// Cap at max_tokens
//
if(m_tokens > m_max_tokens)
{
m_tokens = m_max_tokens;
}
//
// If m_tokens is < tokens, can't claim.
//
if(m_tokens < tokens)
{
return false;
}
m_tokens -= tokens;
return true;
}
double token_bucket::get_tokens()
{
return m_tokens;
}
uint64_t token_bucket::get_last_seen()
{
return m_last_seen;
}

View File

@@ -1,77 +0,0 @@
/*
Copyright (C) 2019 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <cstdint>
#include <functional>
// A simple token bucket that accumulates tokens at a fixed rate and allows
// for limited bursting in the form of "banked" tokens.
class token_bucket
{
public:
token_bucket();
token_bucket(std::function<uint64_t()> timer);
virtual ~token_bucket();
//
// Initialize the token bucket and start accumulating tokens
//
void init(double rate, double max_tokens, uint64_t now = 0);
//
// Try to claim tokens tokens from the token bucket, using a
// timestamp of now. Returns true if the tokens could be
// claimed. Also updates internal metrics.
//
bool claim(double tokens, uint64_t now);
// Simpler version of claim that claims a single token and
// uses the current time for now
bool claim();
// Return the current number of tokens available
double get_tokens();
// Return the last time someone tried to claim a token.
uint64_t get_last_seen();
private:
std::function<uint64_t()> m_timer;
//
// The number of tokens generated per second.
//
double m_rate;
//
// The maximum number of tokens that can be banked for future
// claim()s.
//
double m_max_tokens;
//
// The current number of tokens
//
double m_tokens;
//
// The last time claim() was called (or the object was created).
// Nanoseconds since the epoch.
//
uint64_t m_last_seen;
};

View File

@@ -103,11 +103,11 @@ if(NOT MINIMAL_BUILD)
"${GPR_LIB}"
"${PROTOBUF_LIB}"
"${CARES_LIB}"
"${OPENSSL_LIBRARY_SSL}"
"${OPENSSL_LIBRARY_CRYPTO}"
"${OPENSSL_LIBRARIES}"
"${LIBYAML_LIB}"
"${YAMLCPP_LIB}"
"${CIVETWEB_LIB}"
"${CIVETWEB_CPP_LIB}"
)
endif()

View File

@@ -28,6 +28,7 @@ limitations under the License.
#define FALCO_SOURCE_DIR "${PROJECT_SOURCE_DIR}"
#define FALCO_SOURCE_CONF_FILE "${PROJECT_SOURCE_DIR}/falco.yaml"
#define FALCO_INSTALL_CONF_FILE "/etc/falco/falco.yaml"
#define FALCO_ENGINE_PLUGINS_DIR "${FALCO_ABSOLUTE_SHARE_DIR}/plugins/"
#define PROBE_NAME "@PROBE_NAME@"
#define DRIVER_VERSION "@PROBE_VERSION@"

Some files were not shown because too many files have changed in this diff Show More