mirror of
https://github.com/falcosecurity/falco.git
synced 2026-03-20 03:32:09 +00:00
Compare commits
19 Commits
add-contex
...
allow-rege
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2475b0f155 | ||
|
|
e01d3d68a3 | ||
|
|
8d3cf12522 | ||
|
|
126085dc4f | ||
|
|
f0299065d8 | ||
|
|
01f65e3bae | ||
|
|
1711ed0a2e | ||
|
|
839d76a760 | ||
|
|
dc7bff127f | ||
|
|
e80ff6296a | ||
|
|
231f881c5a | ||
|
|
cb5a3a14e6 | ||
|
|
4c68da0dcc | ||
|
|
a32870ae1d | ||
|
|
fdbd520cce | ||
|
|
f20a5a04bf | ||
|
|
affb1086a3 | ||
|
|
8155d467ab | ||
|
|
bf19d8c881 |
119
.cmake-format
Normal file
119
.cmake-format
Normal file
@@ -0,0 +1,119 @@
|
||||
# --------------------------
|
||||
# General Formatting Options
|
||||
# --------------------------
|
||||
# How wide to allow formatted cmake files
|
||||
line_width = 80
|
||||
|
||||
# How many spaces to tab for indent
|
||||
tab_size = 2
|
||||
|
||||
# If arglists are longer than this, break them always
|
||||
max_subargs_per_line = 3
|
||||
|
||||
# If true, separate flow control names from their parentheses with a space
|
||||
separate_ctrl_name_with_space = False
|
||||
|
||||
# If true, separate function names from parentheses with a space
|
||||
separate_fn_name_with_space = False
|
||||
|
||||
# If a statement is wrapped to more than one line, than dangle the closing
|
||||
# parenthesis on it's own line
|
||||
dangle_parens = False
|
||||
|
||||
# If the statement spelling length (including space and parenthesis is larger
|
||||
# than the tab width by more than this amoung, then force reject un-nested
|
||||
# layouts.
|
||||
max_prefix_chars = 2
|
||||
|
||||
# If a candidate layout is wrapped horizontally but it exceeds this many lines,
|
||||
# then reject the layout.
|
||||
max_lines_hwrap = 2
|
||||
|
||||
# What style line endings to use in the output.
|
||||
line_ending = 'unix'
|
||||
|
||||
# Format command names consistently as 'lower' or 'upper' case
|
||||
command_case = 'canonical'
|
||||
|
||||
# Format keywords consistently as 'lower' or 'upper' case
|
||||
keyword_case = 'unchanged'
|
||||
|
||||
# Specify structure for custom cmake functions
|
||||
additional_commands = {
|
||||
"pkg_find": {
|
||||
"kwargs": {
|
||||
"PKG": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# A list of command names which should always be wrapped
|
||||
always_wrap = []
|
||||
|
||||
# Specify the order of wrapping algorithms during successive reflow attempts
|
||||
algorithm_order = [0, 1, 2, 3, 4]
|
||||
|
||||
# If true, the argument lists which are known to be sortable will be sorted
|
||||
# lexicographicall
|
||||
enable_sort = True
|
||||
|
||||
# If true, the parsers may infer whether or not an argument list is sortable
|
||||
# (without annotation).
|
||||
autosort = False
|
||||
|
||||
# If a comment line starts with at least this many consecutive hash characters,
|
||||
# then don't lstrip() them off. This allows for lazy hash rulers where the first
|
||||
# hash char is not separated by space
|
||||
hashruler_min_length = 10
|
||||
|
||||
# A dictionary containing any per-command configuration overrides. Currently
|
||||
# only `command_case` is supported.
|
||||
per_command = {}
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Comment Formatting Options
|
||||
# --------------------------
|
||||
# What character to use for bulleted lists
|
||||
bullet_char = '*'
|
||||
|
||||
# What character to use as punctuation after numerals in an enumerated list
|
||||
enum_char = '.'
|
||||
|
||||
# enable comment markup parsing and reflow
|
||||
enable_markup = True
|
||||
|
||||
# If comment markup is enabled, don't reflow the first comment block in each
|
||||
# listfile. Use this to preserve formatting of your copyright/license
|
||||
# statements.
|
||||
first_comment_is_literal = False
|
||||
|
||||
# If comment markup is enabled, don't reflow any comment block which matches
|
||||
# this (regex) pattern. Default is `None` (disabled).
|
||||
literal_comment_pattern = None
|
||||
|
||||
# Regular expression to match preformat fences in comments
|
||||
# default=r'^\s*([`~]{3}[`~]*)(.*)$'
|
||||
fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$'
|
||||
|
||||
# Regular expression to match rulers in comments
|
||||
# default=r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'
|
||||
ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$'
|
||||
|
||||
# If true, then insert a space between the first hash char and remaining hash
|
||||
# chars in a hash ruler, and normalize it's length to fill the column
|
||||
canonicalize_hashrulers = True
|
||||
|
||||
|
||||
# ---------------------------------
|
||||
# Miscellaneous Options
|
||||
# ---------------------------------
|
||||
# If true, emit the unicode byte-order mark (BOM) at the start of the file
|
||||
emit_byteorder_mark = False
|
||||
|
||||
# Specify the encoding of the input file. Defaults to utf-8.
|
||||
input_encoding = 'utf-8'
|
||||
|
||||
# Specify the encoding of the output file. Defaults to utf-8. Note that cmake
|
||||
# only claims to support utf-8 so be careful when using anything else
|
||||
output_encoding = 'utf-8'
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -20,4 +20,7 @@ docker/event-generator/mysqld
|
||||
docker/event-generator/httpd
|
||||
docker/event-generator/sha1sum
|
||||
docker/event-generator/vipw
|
||||
.vscode/*
|
||||
|
||||
.vscode/*
|
||||
|
||||
.luacheckcache
|
||||
9
.luacheckrc
Normal file
9
.luacheckrc
Normal file
@@ -0,0 +1,9 @@
|
||||
std = "min"
|
||||
cache = true
|
||||
include_files = {
|
||||
"userspace/falco/lua/*.lua",
|
||||
"userspace/engine/lua/*.lua",
|
||||
"userspace/engine/lua/lyaml/*.lua",
|
||||
"*.luacheckrc"
|
||||
}
|
||||
exclude_files = {"build"}
|
||||
8
.yamllint.conf
Normal file
8
.yamllint.conf
Normal file
@@ -0,0 +1,8 @@
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
indentation: disable
|
||||
document-start: disable
|
||||
comments: disable
|
||||
line-length: disable
|
||||
new-line-at-end-of-file: disable
|
||||
@@ -1740,7 +1740,7 @@
|
||||
docker.io/sysdig/agent, docker.io/sysdig/falco, docker.io/sysdig/sysdig,
|
||||
gcr.io/google_containers/kube-proxy, docker.io/calico/node,
|
||||
docker.io/rook/toolbox, docker.io/cloudnativelabs/kube-router, docker.io/mesosphere/mesos-slave,
|
||||
docker.io/docker/ucp-agent, sematext_images
|
||||
docker.io/docker/ucp-agent, sematext_images, k8s.gcr.io/kube-proxy
|
||||
]
|
||||
|
||||
- macro: falco_privileged_containers
|
||||
@@ -1832,7 +1832,7 @@
|
||||
# when we lose events and lose track of state.
|
||||
|
||||
- macro: container_entrypoint
|
||||
condition: (not proc.pname exists or proc.pname in (runc:[0:PARENT], runc:[1:CHILD], docker-runc, exe))
|
||||
condition: (not proc.pname exists or proc.pname in (runc:[0:PARENT], runc:[1:CHILD], runc, docker-runc, exe))
|
||||
|
||||
- rule: Launch Sensitive Mount Container
|
||||
desc: >
|
||||
@@ -2253,7 +2253,7 @@
|
||||
condition: >
|
||||
spawned_process and container and
|
||||
((proc.name = "nc" and (proc.args contains "-e" or proc.args contains "-c")) or
|
||||
(proc.name = "ncat" and (proc.args contains "--sh-exec" or proc.args contains "--exec" or proc.args contains "-e "
|
||||
(proc.name = "ncat" and (proc.args contains "--sh-exec" or proc.args contains "--exec" or proc.args contains "-e "
|
||||
or proc.args contains "-c " or proc.args contains "--lua-exec"))
|
||||
)
|
||||
output: >
|
||||
|
||||
@@ -41,6 +41,9 @@ class FalcoTest(Test):
|
||||
build_dir = os.path.join('/build', build_type)
|
||||
self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, build_dir))
|
||||
|
||||
self.stdout_is = self.params.get('stdout_is', '*', default='')
|
||||
self.stderr_is = self.params.get('stderr_is', '*', default='')
|
||||
|
||||
self.stdout_contains = self.params.get('stdout_contains', '*', default='')
|
||||
|
||||
if not isinstance(self.stdout_contains, list):
|
||||
@@ -83,8 +86,21 @@ class FalcoTest(Test):
|
||||
if not isinstance(self.rules_file, list):
|
||||
self.rules_file = [self.rules_file]
|
||||
|
||||
self.validate_rules_file = self.params.get('validate_rules_file', '*', default=False)
|
||||
|
||||
if self.validate_rules_file == False:
|
||||
self.validate_rules_file = []
|
||||
else:
|
||||
if not isinstance(self.validate_rules_file, list):
|
||||
self.validate_rules_file = [self.validate_rules_file]
|
||||
|
||||
self.rules_args = ""
|
||||
|
||||
for file in self.validate_rules_file:
|
||||
if not os.path.isabs(file):
|
||||
file = os.path.join(self.basedir, file)
|
||||
self.rules_args = self.rules_args + "-V " + file + " "
|
||||
|
||||
for file in self.rules_file:
|
||||
if not os.path.isabs(file):
|
||||
file = os.path.join(self.basedir, file)
|
||||
@@ -252,7 +268,7 @@ class FalcoTest(Test):
|
||||
triggered_rules = match.group(1)
|
||||
|
||||
for rule, count in self.detect_counts.iteritems():
|
||||
expected = '\s{}: (\d+)'.format(rule)
|
||||
expected = '\s{}: (\d+)'.format(re.sub(r'([$\.*+?()[\]{}|^])', r'\\\1', rule))
|
||||
match = re.search(expected, triggered_rules)
|
||||
|
||||
if match is None:
|
||||
@@ -433,6 +449,15 @@ class FalcoTest(Test):
|
||||
|
||||
res = self.falco_proc.run(timeout=180, sig=9)
|
||||
|
||||
if self.stdout_is != '':
|
||||
print(self.stdout_is)
|
||||
if self.stdout_is != res.stdout:
|
||||
self.fail("Stdout was not exactly {}".format(self.stdout_is))
|
||||
|
||||
if self.stderr_is != '':
|
||||
if self.stderr_is != res.stdout:
|
||||
self.fail("Stdout was not exactly {}".format(self.stderr_is))
|
||||
|
||||
for pattern in self.stderr_contains:
|
||||
match = re.search(pattern, res.stderr)
|
||||
if match is None:
|
||||
|
||||
@@ -86,6 +86,15 @@ trace_files: !mux
|
||||
- rules/rule_names_with_spaces.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
rule_names_with_regex_chars:
|
||||
detect: True
|
||||
detect_level: WARNING
|
||||
rules_file:
|
||||
- rules/rule_names_with_regex_chars.yaml
|
||||
detect_counts:
|
||||
- 'Open From Cat ($\.*+?()[]{}|^)': 8
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
multiple_rules_first_empty:
|
||||
detect: True
|
||||
detect_level: WARNING
|
||||
@@ -238,6 +247,199 @@ trace_files: !mux
|
||||
- rules/endswith.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_not_yaml:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Rules content is not yaml
|
||||
---
|
||||
This is not yaml
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_not_yaml.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_not_array:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Rules content is not yaml array of objects
|
||||
---
|
||||
foo: bar
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_not_array.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_array_item_not_object:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Unexpected element of type string. Each element should be a yaml associative array.
|
||||
---
|
||||
- foo
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_array_item_not_object.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_unexpected object:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Unknown rule object: {foo="bar"}
|
||||
---
|
||||
- foo: bar
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_unexpected_object.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_engine_version_not_number:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Value of required_engine_version must be a number
|
||||
---
|
||||
- required_engine_version: not-a-number
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_engine_version_not_number.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_yaml_parse_error:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
mapping values are not allowed in this context
|
||||
---
|
||||
this : is : not : yaml
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_yaml_parse_error.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_list_without_items:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
List must have property items
|
||||
---
|
||||
- list: bad_list
|
||||
no_items: foo
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_list_without_items.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_macro_without_condition:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Macro must have property condition
|
||||
---
|
||||
- macro: bad_macro
|
||||
nope: 1
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_macro_without_condition.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_rule_without_output:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Rule must have property output
|
||||
---
|
||||
- rule: no output rule
|
||||
desc: some desc
|
||||
condition: evt.type=fork
|
||||
priority: INFO
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_rule_without_output.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_append_rule_without_condition:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Rule must have property condition
|
||||
---
|
||||
- rule: no condition rule
|
||||
append: true
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_append_rule_without_condition.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_append_macro_dangling:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Macro dangling append has 'append' key but no macro by that name already exists
|
||||
---
|
||||
- macro: dangling append
|
||||
condition: and evt.type=execve
|
||||
append: true
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_append_macro_dangling.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_list_append_dangling:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
List my_list has 'append' key but no list by that name already exists
|
||||
---
|
||||
- list: my_list
|
||||
items: [not-cat]
|
||||
append: true
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/list_append_failure.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_rule_append_dangling:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Rule my_rule has 'append' key but no rule by that name already exists
|
||||
---
|
||||
- rule: my_rule
|
||||
condition: evt.type=open
|
||||
append: true
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/rule_append_failure.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_missing_rule_name:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Rule name is empty
|
||||
---
|
||||
- rule:
|
||||
desc: some desc
|
||||
condition: evt.type=execve
|
||||
output: some output
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_missing_rule_name.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_missing_list_name:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
List name is empty
|
||||
---
|
||||
- list:
|
||||
items: [foo]
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_missing_list_name.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_missing_macro_name:
|
||||
exit_status: 1
|
||||
stdout_is: |+
|
||||
Macro name is empty
|
||||
---
|
||||
- macro:
|
||||
condition: evt.type=execve
|
||||
---
|
||||
validate_rules_file:
|
||||
- rules/invalid_missing_macro_name.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
invalid_rule_output:
|
||||
exit_status: 1
|
||||
stderr_contains: "Runtime error: Error loading rules:.* Invalid output format 'An open was seen %not_a_real_field': 'invalid formatting token not_a_real_field'. Exiting."
|
||||
@@ -254,13 +456,13 @@ trace_files: !mux
|
||||
- open_from_cat
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
disabled_rules_using_regex:
|
||||
disabled_rules_using_substring:
|
||||
detect: False
|
||||
rules_file:
|
||||
- rules/empty_rules.yaml
|
||||
- rules/single_rule.yaml
|
||||
disabled_rules:
|
||||
- "open.*"
|
||||
- "open_from"
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
disabled_rules_using_enabled_flag:
|
||||
@@ -601,7 +803,7 @@ trace_files: !mux
|
||||
|
||||
list_append_failure:
|
||||
exit_status: 1
|
||||
stderr_contains: "List my_list has 'append' key but no list by that name already exists. Exiting"
|
||||
stderr_contains: "List my_list has 'append' key but no list by that name already exists"
|
||||
rules_file:
|
||||
- rules/list_append_failure.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
@@ -621,7 +823,7 @@ trace_files: !mux
|
||||
|
||||
macro_append_failure:
|
||||
exit_status: 1
|
||||
stderr_contains: "Macro my_macro has 'append' key but no macro by that name already exists. Exiting"
|
||||
stderr_contains: "Macro my_macro has 'append' key but no macro by that name already exists"
|
||||
rules_file:
|
||||
- rules/macro_append_failure.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
@@ -641,7 +843,7 @@ trace_files: !mux
|
||||
|
||||
rule_append_failure:
|
||||
exit_status: 1
|
||||
stderr_contains: "Rule my_rule has 'append' key but no rule by that name already exists. Exiting"
|
||||
stderr_contains: "Rule my_rule has 'append' key but no rule by that name already exists"
|
||||
rules_file:
|
||||
- rules/rule_append_failure.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
3
test/rules/invalid_append_macro_dangling.yaml
Normal file
3
test/rules/invalid_append_macro_dangling.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
- macro: dangling append
|
||||
condition: and evt.type=execve
|
||||
append: true
|
||||
2
test/rules/invalid_append_rule_without_condition.yaml
Normal file
2
test/rules/invalid_append_rule_without_condition.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- rule: no condition rule
|
||||
append: true
|
||||
1
test/rules/invalid_array_item_not_object.yaml
Normal file
1
test/rules/invalid_array_item_not_object.yaml
Normal file
@@ -0,0 +1 @@
|
||||
- foo
|
||||
5
test/rules/invalid_condition_not_rule.yaml
Normal file
5
test/rules/invalid_condition_not_rule.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
- rule: condition not rule
|
||||
condition:
|
||||
desc: some desc
|
||||
output: some output
|
||||
priority: INFO
|
||||
34
test/rules/invalid_engine_version_not_number.yaml
Normal file
34
test/rules/invalid_engine_version_not_number.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# Copyright (C) 2016-2018 Draios Inc dba Sysdig.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
- required_engine_version: not-a-number
|
||||
|
||||
- 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
|
||||
5
test/rules/invalid_list_without_items.yaml
Normal file
5
test/rules/invalid_list_without_items.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
- list: good_list
|
||||
items: [foo]
|
||||
|
||||
- list: bad_list
|
||||
no_items: foo
|
||||
2
test/rules/invalid_macro_comple_error.yaml
Normal file
2
test/rules/invalid_macro_comple_error.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- macro: macro with comp error
|
||||
condition: gak
|
||||
6
test/rules/invalid_macro_without_condition.yaml
Normal file
6
test/rules/invalid_macro_without_condition.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- macro: bad_macro
|
||||
nope: 1
|
||||
|
||||
- macro: good_macro
|
||||
condition: evt.type=execve
|
||||
|
||||
2
test/rules/invalid_missing_list_name.yaml
Normal file
2
test/rules/invalid_missing_list_name.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- list:
|
||||
items: [foo]
|
||||
2
test/rules/invalid_missing_macro_name.yaml
Normal file
2
test/rules/invalid_missing_macro_name.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- macro:
|
||||
condition: evt.type=execve
|
||||
4
test/rules/invalid_missing_rule_name.yaml
Normal file
4
test/rules/invalid_missing_rule_name.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
- rule:
|
||||
desc: some desc
|
||||
condition: evt.type=execve
|
||||
output: some output
|
||||
1
test/rules/invalid_not_array.yaml
Normal file
1
test/rules/invalid_not_array.yaml
Normal file
@@ -0,0 +1 @@
|
||||
foo: bar
|
||||
1
test/rules/invalid_not_yaml.yaml
Normal file
1
test/rules/invalid_not_yaml.yaml
Normal file
@@ -0,0 +1 @@
|
||||
This is not yaml
|
||||
4
test/rules/invalid_rule_without_output.yaml
Normal file
4
test/rules/invalid_rule_without_output.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
- rule: no output rule
|
||||
desc: some desc
|
||||
condition: evt.type=fork
|
||||
priority: INFO
|
||||
1
test/rules/invalid_unexpected_object.yaml
Normal file
1
test/rules/invalid_unexpected_object.yaml
Normal file
@@ -0,0 +1 @@
|
||||
- foo: bar
|
||||
1
test/rules/invalid_yaml_parse_error.yaml
Normal file
1
test/rules/invalid_yaml_parse_error.yaml
Normal file
@@ -0,0 +1 @@
|
||||
this : is : not : yaml
|
||||
25
test/rules/rule_names_with_regex_chars.yaml
Normal file
25
test/rules/rule_names_with_regex_chars.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# Copyright (C) 2016-2018 Draios Inc dba Sysdig.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
- macro: is_cat
|
||||
condition: proc.name=cat
|
||||
|
||||
- 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
|
||||
@@ -206,17 +206,17 @@ void falco_engine::load_rules_file(const string &rules_filename, bool verbose, b
|
||||
load_rules(rules_content, verbose, all_events, required_engine_version);
|
||||
}
|
||||
|
||||
void falco_engine::enable_rule(const string &pattern, bool enabled, const string &ruleset)
|
||||
void falco_engine::enable_rule(const string &substring, bool enabled, const string &ruleset)
|
||||
{
|
||||
uint16_t ruleset_id = find_ruleset_id(ruleset);
|
||||
|
||||
m_sinsp_rules->enable(pattern, enabled, ruleset_id);
|
||||
m_k8s_audit_rules->enable(pattern, enabled, ruleset_id);
|
||||
m_sinsp_rules->enable(substring, enabled, ruleset_id);
|
||||
m_k8s_audit_rules->enable(substring, enabled, ruleset_id);
|
||||
}
|
||||
|
||||
void falco_engine::enable_rule(const string &pattern, bool enabled)
|
||||
void falco_engine::enable_rule(const string &substring, bool enabled)
|
||||
{
|
||||
enable_rule(pattern, enabled, m_default_ruleset);
|
||||
enable_rule(substring, enabled, m_default_ruleset);
|
||||
}
|
||||
|
||||
void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled, const string &ruleset)
|
||||
|
||||
@@ -76,16 +76,17 @@ public:
|
||||
void load_rules(const std::string &rules_content, bool verbose, bool all_events, uint64_t &required_engine_version);
|
||||
|
||||
//
|
||||
// Enable/Disable any rules matching the provided pattern
|
||||
// (regex). When provided, enable/disable these rules in the
|
||||
// Enable/Disable any rules matching the provided substring.
|
||||
// If the substring is "", all rules are enabled/disabled.
|
||||
// When provided, enable/disable these rules in the
|
||||
// context of the provided ruleset. The ruleset (id) can later
|
||||
// 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 &pattern, bool enabled, const std::string &ruleset);
|
||||
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 &pattern, bool enabled);
|
||||
void enable_rule(const std::string &substring, bool enabled);
|
||||
|
||||
//
|
||||
// Enable/Disable any rules with any of the provided tags (set, exact matches only)
|
||||
|
||||
@@ -19,9 +19,9 @@ limitations under the License.
|
||||
|
||||
// The version of rules/filter fields/etc supported by this falco
|
||||
// engine.
|
||||
#define FALCO_ENGINE_VERSION (3)
|
||||
#define FALCO_ENGINE_VERSION (4)
|
||||
|
||||
// This is the result of running "falco --list -N | sha256sum" and
|
||||
// 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 "9b5557ec8f16f5606a1544573b152d211d5212f653ee039146836a17266ff449"
|
||||
#define FALCO_FIELDS_CHECKSUM "ceb069d9f9b2d4ebcc5de39bddc53b7af2e6b8f072edc293668fd6ac4e532413"
|
||||
|
||||
@@ -19,8 +19,8 @@ limitations under the License.
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "uri.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "falco_common.h"
|
||||
#include "json_evt.h"
|
||||
@@ -30,7 +30,6 @@ using namespace std;
|
||||
|
||||
json_event::json_event()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
json_event::~json_event()
|
||||
@@ -60,7 +59,7 @@ std::string json_event_filter_check::def_format(const json &j, std::string &fiel
|
||||
|
||||
std::string json_event_filter_check::json_as_string(const json &j)
|
||||
{
|
||||
if (j.type() == json::value_t::string)
|
||||
if(j.type() == json::value_t::string)
|
||||
{
|
||||
return j;
|
||||
}
|
||||
@@ -70,32 +69,35 @@ std::string json_event_filter_check::json_as_string(const json &j)
|
||||
}
|
||||
}
|
||||
|
||||
json_event_filter_check::field_info::field_info()
|
||||
: m_idx_mode(IDX_NONE), m_idx_type(IDX_NUMERIC)
|
||||
json_event_filter_check::field_info::field_info():
|
||||
m_idx_mode(IDX_NONE), m_idx_type(IDX_NUMERIC)
|
||||
{
|
||||
}
|
||||
|
||||
json_event_filter_check::field_info::field_info(std::string name,
|
||||
std::string desc)
|
||||
: m_name(name), m_desc(desc),
|
||||
m_idx_mode(IDX_NONE), m_idx_type(IDX_NUMERIC)
|
||||
std::string desc):
|
||||
m_name(name),
|
||||
m_desc(desc),
|
||||
m_idx_mode(IDX_NONE), m_idx_type(IDX_NUMERIC)
|
||||
{
|
||||
}
|
||||
|
||||
json_event_filter_check::field_info::field_info(std::string name,
|
||||
std::string desc,
|
||||
index_mode mode)
|
||||
: m_name(name), m_desc(desc),
|
||||
m_idx_mode(mode), m_idx_type(IDX_NUMERIC)
|
||||
index_mode mode):
|
||||
m_name(name),
|
||||
m_desc(desc),
|
||||
m_idx_mode(mode), m_idx_type(IDX_NUMERIC)
|
||||
{
|
||||
}
|
||||
|
||||
json_event_filter_check::field_info::field_info(std::string name,
|
||||
std::string desc,
|
||||
index_mode mode,
|
||||
index_type itype)
|
||||
: m_name(name), m_desc(desc),
|
||||
m_idx_mode(mode), m_idx_type(itype)
|
||||
index_type itype):
|
||||
m_name(name),
|
||||
m_desc(desc),
|
||||
m_idx_mode(mode), m_idx_type(itype)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -107,14 +109,15 @@ json_event_filter_check::alias::alias()
|
||||
{
|
||||
}
|
||||
|
||||
json_event_filter_check::alias::alias(nlohmann::json::json_pointer ptr)
|
||||
: m_jptr(ptr), m_format(def_format)
|
||||
json_event_filter_check::alias::alias(nlohmann::json::json_pointer ptr):
|
||||
m_jptr(ptr), m_format(def_format)
|
||||
{
|
||||
}
|
||||
|
||||
json_event_filter_check::alias::alias(nlohmann::json::json_pointer ptr,
|
||||
format_t format)
|
||||
: m_jptr(ptr), m_format(format)
|
||||
format_t format):
|
||||
m_jptr(ptr),
|
||||
m_format(format)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -122,8 +125,8 @@ json_event_filter_check::alias::~alias()
|
||||
{
|
||||
}
|
||||
|
||||
json_event_filter_check::json_event_filter_check()
|
||||
: m_format(def_format)
|
||||
json_event_filter_check::json_event_filter_check():
|
||||
m_format(def_format)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -150,7 +153,7 @@ int32_t json_event_filter_check::parse_field_name(const char *str, bool alloc_st
|
||||
|
||||
// What follows the match must not be alphanumeric or a dot
|
||||
if(strncmp(info.m_name.c_str(), str, info.m_name.size()) == 0 &&
|
||||
!isalnum((int) str[info.m_name.size()]) &&
|
||||
!isalnum((int)str[info.m_name.size()]) &&
|
||||
str[info.m_name.size()] != '.' &&
|
||||
info.m_name.size() > match_len)
|
||||
{
|
||||
@@ -169,7 +172,7 @@ int32_t json_event_filter_check::parse_field_name(const char *str, bool alloc_st
|
||||
|
||||
if(end != NULL)
|
||||
{
|
||||
m_idx = string(start, end-start);
|
||||
m_idx = string(start, end - start);
|
||||
}
|
||||
|
||||
idx_len = (end - start + 2);
|
||||
@@ -197,14 +200,14 @@ int32_t json_event_filter_check::parse_field_name(const char *str, bool alloc_st
|
||||
return match_len + idx_len;
|
||||
}
|
||||
|
||||
void json_event_filter_check::add_filter_value(const char* str, uint32_t len, uint32_t i)
|
||||
void json_event_filter_check::add_filter_value(const char *str, uint32_t len, uint32_t i)
|
||||
{
|
||||
m_values.push_back(string(str));
|
||||
}
|
||||
|
||||
bool json_event_filter_check::compare(gen_event *evt)
|
||||
{
|
||||
json_event *jevt = (json_event *) evt;
|
||||
json_event *jevt = (json_event *)evt;
|
||||
|
||||
std::string value = extract(jevt);
|
||||
|
||||
@@ -225,7 +228,7 @@ bool json_event_filter_check::compare(gen_event *evt)
|
||||
case CO_IN:
|
||||
for(auto &val : m_values)
|
||||
{
|
||||
if (value == val)
|
||||
if(value == val)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -268,11 +271,12 @@ json_event_filter_check::check_info &json_event_filter_check::get_fields()
|
||||
return m_info;
|
||||
}
|
||||
|
||||
uint8_t* json_event_filter_check::extract(gen_event *evt, uint32_t* len, bool sanitize_strings)
|
||||
uint8_t *json_event_filter_check::extract(gen_event *evt, uint32_t *len, bool sanitize_strings)
|
||||
{
|
||||
json_event *jevt = (json_event *) evt;
|
||||
json_event *jevt = (json_event *)evt;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const json &j = jevt->jevt().at(m_jptr);
|
||||
|
||||
// Only format when the value was actually found in
|
||||
@@ -286,7 +290,7 @@ uint8_t* json_event_filter_check::extract(gen_event *evt, uint32_t* len, bool sa
|
||||
|
||||
*len = m_tstr.size();
|
||||
|
||||
return (uint8_t *) m_tstr.c_str();
|
||||
return (uint8_t *)m_tstr.c_str();
|
||||
}
|
||||
|
||||
std::string json_event_filter_check::extract(json_event *evt)
|
||||
@@ -299,7 +303,7 @@ std::string json_event_filter_check::extract(json_event *evt)
|
||||
|
||||
if(res != NULL)
|
||||
{
|
||||
ret.assign((const char *) res, len);
|
||||
ret.assign((const char *)res, len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -315,18 +319,15 @@ jevt_filter_check::jevt_filter_check()
|
||||
{
|
||||
m_info = {"jevt",
|
||||
"generic ways to access json events",
|
||||
{
|
||||
{s_jevt_time_field, "json event timestamp as a string that includes the nanosecond part"},
|
||||
{s_jevt_time_iso_8601_field, "json event timestamp in ISO 8601 format, including nanoseconds and time zone offset (in UTC)"},
|
||||
{s_jevt_rawtime_field, "absolute event timestamp, i.e. nanoseconds from epoch."},
|
||||
{s_jevt_value_field, "General way to access single property from json object. The syntax is [<json pointer expression>]. The property is returned as a string", IDX_REQUIRED, IDX_KEY},
|
||||
{s_jevt_obj_field, "The entire json object, stringified"}
|
||||
}};
|
||||
{{s_jevt_time_field, "json event timestamp as a string that includes the nanosecond part"},
|
||||
{s_jevt_time_iso_8601_field, "json event timestamp in ISO 8601 format, including nanoseconds and time zone offset (in UTC)"},
|
||||
{s_jevt_rawtime_field, "absolute event timestamp, i.e. nanoseconds from epoch."},
|
||||
{s_jevt_value_field, "General way to access single property from json object. The syntax is [<json pointer expression>]. The property is returned as a string", IDX_REQUIRED, IDX_KEY},
|
||||
{s_jevt_obj_field, "The entire json object, stringified"}}};
|
||||
}
|
||||
|
||||
jevt_filter_check::~jevt_filter_check()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int32_t jevt_filter_check::parse_field_name(const char *str, bool alloc_state, bool needed_for_filtering)
|
||||
@@ -360,55 +361,56 @@ int32_t jevt_filter_check::parse_field_name(const char *str, bool alloc_state, b
|
||||
const char *end;
|
||||
|
||||
// What follows must be [<json pointer expression>]
|
||||
if (*(str + s_jevt_value_field.size()) != '[' ||
|
||||
((end = strchr(str + 1, ']')) == NULL))
|
||||
if(*(str + s_jevt_value_field.size()) != '[' ||
|
||||
((end = strchr(str + 1, ']')) == NULL))
|
||||
|
||||
{
|
||||
throw falco_exception(string("Could not parse filtercheck field \"") + str + "\". Did not have expected format with 'jevt.value[<json pointer>]'");
|
||||
}
|
||||
|
||||
try {
|
||||
m_jptr = json::json_pointer(string(str + (s_jevt_value_field.size()+1), (end-str-(s_jevt_value_field.size()+1))));
|
||||
try
|
||||
{
|
||||
m_jptr = json::json_pointer(string(str + (s_jevt_value_field.size() + 1), (end - str - (s_jevt_value_field.size() + 1))));
|
||||
}
|
||||
catch (json::parse_error& e)
|
||||
catch(json::parse_error &e)
|
||||
{
|
||||
throw falco_exception(string("Could not parse filtercheck field \"") + str + "\". Invalid json selector (" + e.what() + ")");
|
||||
}
|
||||
|
||||
// The +1 accounts for the closing ']'
|
||||
m_field = string(str, end-str + 1);
|
||||
m_field = string(str, end - str + 1);
|
||||
return (end - str + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* jevt_filter_check::extract(gen_event *evt, uint32_t* len, bool sanitize_stings)
|
||||
uint8_t *jevt_filter_check::extract(gen_event *evt, uint32_t *len, bool sanitize_stings)
|
||||
{
|
||||
if(m_field == s_jevt_rawtime_field)
|
||||
{
|
||||
m_tstr = to_string(evt->get_ts());
|
||||
*len = m_tstr.size();
|
||||
return (uint8_t *) m_tstr.c_str();
|
||||
return (uint8_t *)m_tstr.c_str();
|
||||
}
|
||||
else if(m_field == s_jevt_time_field)
|
||||
{
|
||||
sinsp_utils::ts_to_string(evt->get_ts(), &m_tstr, false, true);
|
||||
*len = m_tstr.size();
|
||||
return (uint8_t *) m_tstr.c_str();
|
||||
return (uint8_t *)m_tstr.c_str();
|
||||
}
|
||||
else if(m_field == s_jevt_time_iso_8601_field)
|
||||
{
|
||||
sinsp_utils::ts_to_iso_8601(evt->get_ts(), &m_tstr);
|
||||
*len = m_tstr.size();
|
||||
return (uint8_t *) m_tstr.c_str();
|
||||
return (uint8_t *)m_tstr.c_str();
|
||||
}
|
||||
else if(m_field == s_jevt_obj_field)
|
||||
{
|
||||
json_event *jevt = (json_event *) evt;
|
||||
json_event *jevt = (json_event *)evt;
|
||||
m_tstr = jevt->jevt().dump();
|
||||
*len = m_tstr.size();
|
||||
return (uint8_t *) m_tstr.c_str();
|
||||
return (uint8_t *)m_tstr.c_str();
|
||||
}
|
||||
|
||||
return json_event_filter_check::extract(evt, len, sanitize_stings);
|
||||
@@ -418,7 +420,7 @@ json_event_filter_check *jevt_filter_check::allocate_new()
|
||||
{
|
||||
jevt_filter_check *chk = new jevt_filter_check();
|
||||
|
||||
return (json_event_filter_check *) chk;
|
||||
return (json_event_filter_check *)chk;
|
||||
}
|
||||
|
||||
std::string k8s_audit_filter_check::index_image(const json &j, std::string &field, std::string &idx)
|
||||
@@ -427,8 +429,9 @@ std::string k8s_audit_filter_check::index_image(const json &j, std::string &fiel
|
||||
|
||||
string image;
|
||||
|
||||
try {
|
||||
image = j[idx_num].at("image");
|
||||
try
|
||||
{
|
||||
image = j[idx_num].at("image");
|
||||
}
|
||||
catch(json::out_of_range &e)
|
||||
{
|
||||
@@ -470,7 +473,6 @@ std::string k8s_audit_filter_check::index_has_name(const json &j, std::string &f
|
||||
return string("false");
|
||||
}
|
||||
|
||||
|
||||
std::string k8s_audit_filter_check::index_query_param(const json &j, std::string &field, std::string &idx)
|
||||
{
|
||||
string uri = j;
|
||||
@@ -489,7 +491,7 @@ std::string k8s_audit_filter_check::index_query_param(const json &j, std::string
|
||||
{
|
||||
std::vector<std::string> param_parts = sinsp_split(part, '=');
|
||||
|
||||
if(param_parts.size() == 2 && uri::decode(param_parts[0], true)==idx)
|
||||
if(param_parts.size() == 2 && uri::decode(param_parts[0], true) == idx)
|
||||
{
|
||||
return uri::decode(param_parts[1]);
|
||||
}
|
||||
@@ -498,7 +500,6 @@ std::string k8s_audit_filter_check::index_query_param(const json &j, std::string
|
||||
return string("<NA>");
|
||||
}
|
||||
|
||||
|
||||
std::string k8s_audit_filter_check::index_generic(const json &j, std::string &field, std::string &idx)
|
||||
{
|
||||
json item;
|
||||
@@ -511,7 +512,8 @@ std::string k8s_audit_filter_check::index_generic(const json &j, std::string &fi
|
||||
{
|
||||
uint64_t idx_num = (idx.empty() ? 0 : stoi(idx));
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
item = j[idx_num];
|
||||
}
|
||||
catch(json::out_of_range &e)
|
||||
@@ -529,7 +531,7 @@ std::string k8s_audit_filter_check::index_select(const json &j, std::string &fie
|
||||
|
||||
// Use the suffix of the field to determine which property to
|
||||
// select from each object.
|
||||
std::string prop = field.substr(field.find_last_of(".")+1);
|
||||
std::string prop = field.substr(field.find_last_of(".") + 1);
|
||||
|
||||
std::string ret;
|
||||
|
||||
@@ -542,7 +544,8 @@ std::string k8s_audit_filter_check::index_select(const json &j, std::string &fie
|
||||
ret += " ";
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
ret += json_event_filter_check::json_as_string(obj.at(prop));
|
||||
}
|
||||
catch(json::out_of_range &e)
|
||||
@@ -553,7 +556,8 @@ std::string k8s_audit_filter_check::index_select(const json &j, std::string &fie
|
||||
}
|
||||
else
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
ret = j[stoi(idx)].at(prop);
|
||||
}
|
||||
catch(json::out_of_range &e)
|
||||
@@ -573,7 +577,8 @@ std::string k8s_audit_filter_check::index_privileged(const json &j, std::string
|
||||
|
||||
if(!idx.empty())
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
privileged = j[stoi(idx)].at(jpriv);
|
||||
}
|
||||
catch(json::out_of_range &e)
|
||||
@@ -584,7 +589,8 @@ std::string k8s_audit_filter_check::index_privileged(const json &j, std::string
|
||||
{
|
||||
for(auto &container : j)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
if(container.at(jpriv))
|
||||
{
|
||||
privileged = true;
|
||||
@@ -621,42 +627,41 @@ k8s_audit_filter_check::k8s_audit_filter_check()
|
||||
{
|
||||
m_info = {"ka",
|
||||
"Access K8s Audit Log Events",
|
||||
{
|
||||
{"ka.auditid", "The unique id of the audit event"},
|
||||
{"ka.stage", "Stage of the request (e.g. RequestReceived, ResponseComplete, etc.)"},
|
||||
{"ka.auth.decision", "The authorization decision"},
|
||||
{"ka.auth.reason", "The authorization reason"},
|
||||
{"ka.user.name", "The user name performing the request"},
|
||||
{"ka.user.groups", "The groups to which the user belongs"},
|
||||
{"ka.impuser.name", "The impersonated user name"},
|
||||
{"ka.verb", "The action being performed"},
|
||||
{"ka.uri", "The request URI as sent from client to server"},
|
||||
{"ka.uri.param", "The value of a given query parameter in the uri (e.g. when uri=/foo?key=val, ka.uri.param[key] is val).", IDX_REQUIRED, IDX_KEY},
|
||||
{"ka.target.name", "The target object name"},
|
||||
{"ka.target.namespace", "The target object namespace"},
|
||||
{"ka.target.resource", "The target object resource"},
|
||||
{"ka.target.subresource", "The target object subresource"},
|
||||
{"ka.req.binding.subjects", "When the request object refers to a cluster role binding, the subject (e.g. account/users) being linked by the binding"},
|
||||
{"ka.req.binding.subject.has_name", "When the request object refers to a cluster role binding, return true if a subject with the provided name exists", IDX_REQUIRED, IDX_KEY},
|
||||
{"ka.req.binding.role", "When the request object refers to a cluster role binding, the role being linked by the binding"},
|
||||
{"ka.req.configmap.name", "If the request object refers to a configmap, the configmap name"},
|
||||
{"ka.req.configmap.obj", "If the request object refers to a configmap, the entire configmap object"},
|
||||
{"ka.req.container.image", "When the request object refers to a container, the container's images. Can be indexed (e.g. ka.req.container.image[0]). Without any index, returns the first image", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.container.image.repository", "The same as req.container.image, but only the repository part (e.g. sysdig/falco)", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.container.host_network", "When the request object refers to a container, the value of the hostNetwork flag."},
|
||||
{"ka.req.container.privileged", "When the request object refers to a container, whether or not any container is run privileged. With an index, return whether or not the ith container is run privileged.", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules", "When the request object refers to a role/cluster role, the rules associated with the role"},
|
||||
{"ka.req.role.rules.apiGroups", "When the request object refers to a role/cluster role, the api groups associated with the role's rules. With an index, return only the api groups from the ith rule. Without an index, return all api groups concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules.nonResourceURLs", "When the request object refers to a role/cluster role, the non resource urls associated with the role's rules. With an index, return only the non resource urls from the ith rule. Without an index, return all non resource urls concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules.verbs", "When the request object refers to a role/cluster role, the verbs associated with the role's rules. With an index, return only the verbs from the ith rule. Without an index, return all verbs concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules.resources", "When the request object refers to a role/cluster role, the resources associated with the role's rules. With an index, return only the resources from the ith rule. Without an index, return all resources concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.service.type", "When the request object refers to a service, the service type"},
|
||||
{"ka.req.service.ports", "When the request object refers to a service, the service's ports. Can be indexed (e.g. ka.req.service.ports[0]). Without any index, returns all ports", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.volume.hostpath", "If the request object contains volume definitions, whether or not a hostPath volume exists that mounts the specified path from the host (...hostpath[/etc]=true if a volume mounts /etc from the host). The index can be a glob, in which case all volumes are considered to find any path matching the specified glob (...hostpath[/usr/*] would match either /usr/local or /usr/bin)", IDX_REQUIRED, IDX_KEY},
|
||||
{"ka.resp.name", "The response object name"},
|
||||
{"ka.response.code", "The response code"},
|
||||
{"ka.response.reason", "The response reason (usually present only for failures)"}
|
||||
}};
|
||||
{{"ka.auditid", "The unique id of the audit event"},
|
||||
{"ka.stage", "Stage of the request (e.g. RequestReceived, ResponseComplete, etc.)"},
|
||||
{"ka.auth.decision", "The authorization decision"},
|
||||
{"ka.auth.reason", "The authorization reason"},
|
||||
{"ka.user.name", "The user name performing the request"},
|
||||
{"ka.user.groups", "The groups to which the user belongs"},
|
||||
{"ka.impuser.name", "The impersonated user name"},
|
||||
{"ka.verb", "The action being performed"},
|
||||
{"ka.uri", "The request URI as sent from client to server"},
|
||||
{"ka.uri.param", "The value of a given query parameter in the uri (e.g. when uri=/foo?key=val, ka.uri.param[key] is val).", IDX_REQUIRED, IDX_KEY},
|
||||
{"ka.target.name", "The target object name"},
|
||||
{"ka.target.namespace", "The target object namespace"},
|
||||
{"ka.target.resource", "The target object resource"},
|
||||
{"ka.target.subresource", "The target object subresource"},
|
||||
{"ka.req.binding.subjects", "When the request object refers to a cluster role binding, the subject (e.g. account/users) being linked by the binding"},
|
||||
{"ka.req.binding.subject.has_name", "When the request object refers to a cluster role binding, return true if a subject with the provided name exists", IDX_REQUIRED, IDX_KEY},
|
||||
{"ka.req.binding.role", "When the request object refers to a cluster role binding, the role being linked by the binding"},
|
||||
{"ka.req.configmap.name", "If the request object refers to a configmap, the configmap name"},
|
||||
{"ka.req.configmap.obj", "If the request object refers to a configmap, the entire configmap object"},
|
||||
{"ka.req.container.image", "When the request object refers to a container, the container's images. Can be indexed (e.g. ka.req.container.image[0]). Without any index, returns the first image", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.container.image.repository", "The same as req.container.image, but only the repository part (e.g. sysdig/falco)", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.container.host_network", "When the request object refers to a container, the value of the hostNetwork flag."},
|
||||
{"ka.req.container.privileged", "When the request object refers to a container, whether or not any container is run privileged. With an index, return whether or not the ith container is run privileged.", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules", "When the request object refers to a role/cluster role, the rules associated with the role"},
|
||||
{"ka.req.role.rules.apiGroups", "When the request object refers to a role/cluster role, the api groups associated with the role's rules. With an index, return only the api groups from the ith rule. Without an index, return all api groups concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules.nonResourceURLs", "When the request object refers to a role/cluster role, the non resource urls associated with the role's rules. With an index, return only the non resource urls from the ith rule. Without an index, return all non resource urls concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules.verbs", "When the request object refers to a role/cluster role, the verbs associated with the role's rules. With an index, return only the verbs from the ith rule. Without an index, return all verbs concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.role.rules.resources", "When the request object refers to a role/cluster role, the resources associated with the role's rules. With an index, return only the resources from the ith rule. Without an index, return all resources concatenated", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.service.type", "When the request object refers to a service, the service type"},
|
||||
{"ka.req.service.ports", "When the request object refers to a service, the service's ports. Can be indexed (e.g. ka.req.service.ports[0]). Without any index, returns all ports", IDX_ALLOWED, IDX_NUMERIC},
|
||||
{"ka.req.volume.hostpath", "If the request object contains volume definitions, whether or not a hostPath volume exists that mounts the specified path from the host (...hostpath[/etc]=true if a volume mounts /etc from the host). The index can be a glob, in which case all volumes are considered to find any path matching the specified glob (...hostpath[/usr/*] would match either /usr/local or /usr/bin)", IDX_REQUIRED, IDX_KEY},
|
||||
{"ka.resp.name", "The response object name"},
|
||||
{"ka.response.code", "The response code"},
|
||||
{"ka.response.reason", "The response reason (usually present only for failures)"},
|
||||
{"ka.useragent", "The useragent of the client who made the request to the apiserver"}}};
|
||||
|
||||
{
|
||||
m_aliases = {
|
||||
@@ -693,21 +698,20 @@ k8s_audit_filter_check::k8s_audit_filter_check()
|
||||
{"ka.req.volume.hostpath", {"/requestObject/spec/volumes"_json_pointer, check_hostpath_vols}},
|
||||
{"ka.resp.name", {"/responseObject/metadata/name"_json_pointer}},
|
||||
{"ka.response.code", {"/responseStatus/code"_json_pointer}},
|
||||
{"ka.response.reason", {"/responseStatus/reason"_json_pointer}}
|
||||
};
|
||||
{"ka.response.reason", {"/responseStatus/reason"_json_pointer}},
|
||||
{"ka.useragent", {"/userAgent"_json_pointer}}};
|
||||
}
|
||||
}
|
||||
|
||||
k8s_audit_filter_check::~k8s_audit_filter_check()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
json_event_filter_check *k8s_audit_filter_check::allocate_new()
|
||||
{
|
||||
k8s_audit_filter_check *chk = new k8s_audit_filter_check();
|
||||
|
||||
return (json_event_filter_check *) chk;
|
||||
return (json_event_filter_check *)chk;
|
||||
}
|
||||
|
||||
json_event_filter::json_event_filter()
|
||||
@@ -762,9 +766,9 @@ std::list<json_event_filter_check::check_info> &json_event_filter_factory::get_f
|
||||
return m_info;
|
||||
}
|
||||
|
||||
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(json_event_filter_factory &json_factory, std::string &format):
|
||||
m_format(format),
|
||||
m_json_factory(json_factory)
|
||||
{
|
||||
parse_format();
|
||||
}
|
||||
@@ -777,7 +781,7 @@ std::string json_event_formatter::tostring(json_event *ev)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
std::list<std::pair<std::string,std::string>> resolved;
|
||||
std::list<std::pair<std::string, std::string>> resolved;
|
||||
|
||||
resolve_tokens(ev, resolved);
|
||||
|
||||
@@ -793,7 +797,7 @@ std::string json_event_formatter::tojson(json_event *ev)
|
||||
{
|
||||
nlohmann::json ret;
|
||||
|
||||
std::list<std::pair<std::string,std::string>> resolved;
|
||||
std::list<std::pair<std::string, std::string>> resolved;
|
||||
|
||||
resolve_tokens(ev, resolved);
|
||||
|
||||
@@ -828,11 +832,11 @@ 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)
|
||||
{
|
||||
throw falco_exception(string ("Could not parse format string \"") + m_format + "\": unknown filtercheck field " + tformat);
|
||||
throw falco_exception(string("Could not parse format string \"") + m_format + "\": unknown filtercheck field " + tformat);
|
||||
}
|
||||
|
||||
size = chk->parsed_size();
|
||||
@@ -852,7 +856,7 @@ void json_event_formatter::parse_format()
|
||||
// Empty fields are only allowed at the beginning of the string
|
||||
if(m_tokens.size() > 0)
|
||||
{
|
||||
throw falco_exception(string ("Could not parse format string \"" + m_format + "\": empty filtercheck field"));
|
||||
throw falco_exception(string("Could not parse format string \"" + m_format + "\": empty filtercheck field"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -864,7 +868,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_tokens(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved)
|
||||
{
|
||||
for(auto tok : m_tokens)
|
||||
{
|
||||
|
||||
@@ -62,12 +62,12 @@ function expand_macros(ast, defs, changed)
|
||||
elseif ast.type == "Filter" then
|
||||
if (ast.value.type == "Macro") then
|
||||
if (defs[ast.value.value] == nil) then
|
||||
error("Undefined macro '".. ast.value.value .. "' used in filter.")
|
||||
return false, "Undefined macro '".. ast.value.value .. "' used in filter."
|
||||
end
|
||||
defs[ast.value.value].used = true
|
||||
ast.value = copy_ast_obj(defs[ast.value.value].ast)
|
||||
changed = true
|
||||
return changed
|
||||
return true, changed
|
||||
end
|
||||
return expand_macros(ast.value, defs, changed)
|
||||
|
||||
@@ -75,7 +75,7 @@ function expand_macros(ast, defs, changed)
|
||||
|
||||
if (ast.left.type == "Macro") then
|
||||
if (defs[ast.left.value] == nil) then
|
||||
error("Undefined macro '".. ast.left.value .. "' used in filter.")
|
||||
return false, "Undefined macro '".. ast.left.value .. "' used in filter."
|
||||
end
|
||||
defs[ast.left.value].used = true
|
||||
ast.left = copy_ast_obj(defs[ast.left.value].ast)
|
||||
@@ -84,21 +84,27 @@ function expand_macros(ast, defs, changed)
|
||||
|
||||
if (ast.right.type == "Macro") then
|
||||
if (defs[ast.right.value] == nil) then
|
||||
error("Undefined macro ".. ast.right.value .. " used in filter.")
|
||||
return false, "Undefined macro ".. ast.right.value .. " used in filter."
|
||||
end
|
||||
defs[ast.right.value].used = true
|
||||
ast.right = copy_ast_obj(defs[ast.right.value].ast)
|
||||
changed = true
|
||||
end
|
||||
|
||||
local changed_left = expand_macros(ast.left, defs, false)
|
||||
local changed_right = expand_macros(ast.right, defs, false)
|
||||
return changed or changed_left or changed_right
|
||||
local status, changed_left = expand_macros(ast.left, defs, false)
|
||||
if status == false then
|
||||
return false, changed_left
|
||||
end
|
||||
local status, changed_right = expand_macros(ast.right, defs, false)
|
||||
if status == false then
|
||||
return false, changed_right
|
||||
end
|
||||
return true, changed or changed_left or changed_right
|
||||
|
||||
elseif ast.type == "UnaryBoolOp" then
|
||||
if (ast.argument.type == "Macro") then
|
||||
if (defs[ast.argument.value] == nil) then
|
||||
error("Undefined macro ".. ast.argument.value .. " used in filter.")
|
||||
return false, "Undefined macro ".. ast.argument.value .. " used in filter."
|
||||
end
|
||||
defs[ast.argument.value].used = true
|
||||
ast.argument = copy_ast_obj(defs[ast.argument.value].ast)
|
||||
@@ -106,7 +112,7 @@ function expand_macros(ast, defs, changed)
|
||||
end
|
||||
return expand_macros(ast.argument, defs, changed)
|
||||
end
|
||||
return changed
|
||||
return true, changed
|
||||
end
|
||||
|
||||
function get_macros(ast, set)
|
||||
@@ -195,7 +201,7 @@ function compiler.compile_macro(line, macro_defs, list_defs)
|
||||
|
||||
if (error_msg) then
|
||||
msg = "Compilation error when compiling \""..line.."\": ".. error_msg
|
||||
error(msg)
|
||||
return false, msg
|
||||
end
|
||||
|
||||
-- Simply as a validation step, try to expand all macros in this
|
||||
@@ -206,14 +212,18 @@ function compiler.compile_macro(line, macro_defs, list_defs)
|
||||
if (ast.type == "Rule") then
|
||||
-- Line is a filter, so expand macro references
|
||||
repeat
|
||||
expanded = expand_macros(ast_copy, macro_defs, false)
|
||||
status, expanded = expand_macros(ast_copy, macro_defs, false)
|
||||
if status == false then
|
||||
msg = "Compilation error when compiling \""..line.."\": ".. expanded
|
||||
return false, msg
|
||||
end
|
||||
until expanded == false
|
||||
|
||||
else
|
||||
error("Unexpected top-level AST type: "..ast.type)
|
||||
return false, "Unexpected top-level AST type: "..ast.type
|
||||
end
|
||||
|
||||
return ast
|
||||
return true, ast
|
||||
end
|
||||
|
||||
--[[
|
||||
@@ -227,22 +237,25 @@ function compiler.compile_filter(name, source, macro_defs, list_defs)
|
||||
|
||||
if (error_msg) then
|
||||
msg = "Compilation error when compiling \""..source.."\": "..error_msg
|
||||
error(msg)
|
||||
return false, msg
|
||||
end
|
||||
|
||||
if (ast.type == "Rule") then
|
||||
-- Line is a filter, so expand macro references
|
||||
repeat
|
||||
expanded = expand_macros(ast, macro_defs, false)
|
||||
status, expanded = expand_macros(ast, macro_defs, false)
|
||||
if status == false then
|
||||
return false, expanded
|
||||
end
|
||||
until expanded == false
|
||||
|
||||
else
|
||||
error("Unexpected top-level AST type: "..ast.type)
|
||||
return false, "Unexpected top-level AST type: "..ast.type
|
||||
end
|
||||
|
||||
filters = get_filters(ast)
|
||||
|
||||
return ast, filters
|
||||
return true, ast, filters
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -179,6 +179,71 @@ function table.tostring( tbl )
|
||||
return "{" .. table.concat( result, "," ) .. "}"
|
||||
end
|
||||
|
||||
-- Split rules_content by lines and also remember the line numbers for
|
||||
-- each top -level object. Returns a table of lines and a table of
|
||||
-- line numbers for objects.
|
||||
|
||||
function split_lines(rules_content)
|
||||
lines = {}
|
||||
indices = {}
|
||||
|
||||
idx = 1
|
||||
last_pos = 1
|
||||
pos = string.find(rules_content, "\n", 1, true)
|
||||
|
||||
while pos ~= nil do
|
||||
line = string.sub(rules_content, last_pos, pos-1)
|
||||
if line ~= "" then
|
||||
lines[#lines+1] = line
|
||||
if string.sub(line, 1, 1) == '-' then
|
||||
indices[#indices+1] = idx
|
||||
end
|
||||
|
||||
idx = idx + 1
|
||||
end
|
||||
|
||||
last_pos = pos+1
|
||||
pos = string.find(rules_content, "\n", pos+1, true)
|
||||
end
|
||||
|
||||
if last_pos < string.len(rules_content) then
|
||||
line = string.sub(rules_content, last_pos)
|
||||
lines[#lines+1] = line
|
||||
if string.sub(line, 1, 1) == '-' then
|
||||
indices[#indices+1] = idx
|
||||
end
|
||||
|
||||
idx = idx + 1
|
||||
end
|
||||
|
||||
-- Add a final index for last line in document
|
||||
indices[#indices+1] = idx
|
||||
|
||||
return lines, indices
|
||||
end
|
||||
|
||||
function get_context(rules_lines, row, num_lines)
|
||||
|
||||
local ret = "---\n"
|
||||
|
||||
idx = row
|
||||
while (idx < (row + num_lines) and idx <= #rules_lines) do
|
||||
ret = ret..rules_lines[idx].."\n"
|
||||
idx = idx + 1
|
||||
end
|
||||
|
||||
ret = ret.."---"
|
||||
|
||||
return ret
|
||||
|
||||
end
|
||||
|
||||
function build_error(rules_lines, row, num_lines, err)
|
||||
|
||||
local ret = err.."\n"..get_context(rules_lines, row, num_lines)
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function load_rules(sinsp_lua_parser,
|
||||
json_lua_parser,
|
||||
@@ -190,16 +255,45 @@ function load_rules(sinsp_lua_parser,
|
||||
replace_container_info,
|
||||
min_priority)
|
||||
|
||||
local rules = yaml.load(rules_content)
|
||||
local required_engine_version = 0
|
||||
|
||||
local lines, indices = split_lines(rules_content)
|
||||
|
||||
local status, rules = pcall(yaml.load, rules_content)
|
||||
|
||||
if status == false then
|
||||
local pat = "^([%d]+):([%d]+): "
|
||||
-- rules is actually an error string
|
||||
|
||||
local row = 0
|
||||
local col = 0
|
||||
|
||||
row, col = string.match(rules, pat)
|
||||
if row ~= nil and col ~= nil then
|
||||
rules = string.gsub(rules, pat, "")
|
||||
end
|
||||
|
||||
row = tonumber(row)
|
||||
col = tonumber(col)
|
||||
|
||||
return false, build_error(lines, row, 3, rules)
|
||||
end
|
||||
|
||||
if rules == nil then
|
||||
-- An empty rules file is acceptable
|
||||
return required_engine_version
|
||||
return true, required_engine_version
|
||||
end
|
||||
|
||||
if type(rules) ~= "table" then
|
||||
error("Rules content \""..rules_content.."\" is not yaml")
|
||||
return false, build_error(lines, 1, 1, "Rules content is not yaml")
|
||||
end
|
||||
|
||||
-- Look for non-numeric indices--implies that document is not array
|
||||
-- of objects.
|
||||
for key, val in pairs(rules) do
|
||||
if type(key) ~= "number" then
|
||||
return false, build_error(lines, 1, 1, "Rules content is not yaml array of objects")
|
||||
end
|
||||
end
|
||||
|
||||
-- Iterate over yaml list. In this pass, all we're doing is
|
||||
@@ -209,17 +303,25 @@ function load_rules(sinsp_lua_parser,
|
||||
for i,v in ipairs(rules) do
|
||||
|
||||
if (not (type(v) == "table")) then
|
||||
error ("Unexpected element of type " ..type(v)..". Each element should be a yaml associative array.")
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Unexpected element of type " ..type(v)..". Each element should be a yaml associative array.")
|
||||
end
|
||||
|
||||
if (v['required_engine_version']) then
|
||||
required_engine_version = v['required_engine_version']
|
||||
if type(required_engine_version) ~= "number" then
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Value of required_engine_version must be a number")
|
||||
end
|
||||
|
||||
if falco_rules.engine_version(rules_mgr) < v['required_engine_version'] then
|
||||
error("Rules require engine version "..v['required_engine_version']..", but engine version is "..falco_rules.engine_version(rules_mgr))
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Rules require engine version "..v['required_engine_version']..", but engine version is "..falco_rules.engine_version(rules_mgr))
|
||||
end
|
||||
|
||||
elseif (v['macro']) then
|
||||
|
||||
if (v['macro'] == nil or type(v['macro']) == "table") then
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Macro name is empty")
|
||||
end
|
||||
|
||||
if v['source'] == nil then
|
||||
v['source'] = "syscall"
|
||||
end
|
||||
@@ -228,9 +330,9 @@ function load_rules(sinsp_lua_parser,
|
||||
state.ordered_macro_names[#state.ordered_macro_names+1] = v['macro']
|
||||
end
|
||||
|
||||
for i, field in ipairs({'condition'}) do
|
||||
for j, field in ipairs({'condition'}) do
|
||||
if (v[field] == nil) then
|
||||
error ("Missing "..field.." in macro with name "..v['macro'])
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Macro must have property "..field)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -243,7 +345,7 @@ function load_rules(sinsp_lua_parser,
|
||||
|
||||
if append then
|
||||
if state.macros_by_name[v['macro']] == nil then
|
||||
error ("Macro " ..v['macro'].. " has 'append' key but no macro by that name already exists")
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Macro " ..v['macro'].. " has 'append' key but no macro by that name already exists")
|
||||
end
|
||||
|
||||
state.macros_by_name[v['macro']]['condition'] = state.macros_by_name[v['macro']]['condition'] .. " " .. v['condition']
|
||||
@@ -254,13 +356,17 @@ function load_rules(sinsp_lua_parser,
|
||||
|
||||
elseif (v['list']) then
|
||||
|
||||
if (v['list'] == nil or type(v['list']) == "table") then
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "List name is empty")
|
||||
end
|
||||
|
||||
if state.lists_by_name[v['list']] == nil then
|
||||
state.ordered_list_names[#state.ordered_list_names+1] = v['list']
|
||||
end
|
||||
|
||||
for i, field in ipairs({'items'}) do
|
||||
for j, field in ipairs({'items'}) do
|
||||
if (v[field] == nil) then
|
||||
error ("Missing "..field.." in list with name "..v['list'])
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "List must have property "..field)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -273,10 +379,10 @@ function load_rules(sinsp_lua_parser,
|
||||
|
||||
if append then
|
||||
if state.lists_by_name[v['list']] == nil then
|
||||
error ("List " ..v['list'].. " has 'append' key but no list by that name already exists")
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "List " ..v['list'].. " has 'append' key but no list by that name already exists")
|
||||
end
|
||||
|
||||
for i, elem in ipairs(v['items']) do
|
||||
for j, elem in ipairs(v['items']) do
|
||||
table.insert(state.lists_by_name[v['list']]['items'], elem)
|
||||
end
|
||||
else
|
||||
@@ -286,7 +392,7 @@ function load_rules(sinsp_lua_parser,
|
||||
elseif (v['rule']) then
|
||||
|
||||
if (v['rule'] == nil or type(v['rule']) == "table") then
|
||||
error ("Missing name in rule")
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Rule name is empty")
|
||||
end
|
||||
|
||||
-- By default, if a rule's condition refers to an unknown
|
||||
@@ -309,15 +415,15 @@ function load_rules(sinsp_lua_parser,
|
||||
if append then
|
||||
|
||||
-- For append rules, all you need is the condition
|
||||
for i, field in ipairs({'condition'}) do
|
||||
for j, field in ipairs({'condition'}) do
|
||||
if (v[field] == nil) then
|
||||
error ("Missing "..field.." in rule with name "..v['rule'])
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Rule must have property "..field)
|
||||
end
|
||||
end
|
||||
|
||||
if state.rules_by_name[v['rule']] == nil then
|
||||
if state.skipped_rules_by_name[v['rule']] == nil then
|
||||
error ("Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists")
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists")
|
||||
end
|
||||
else
|
||||
state.rules_by_name[v['rule']]['condition'] = state.rules_by_name[v['rule']]['condition'] .. " " .. v['condition']
|
||||
@@ -325,9 +431,9 @@ function load_rules(sinsp_lua_parser,
|
||||
|
||||
else
|
||||
|
||||
for i, field in ipairs({'condition', 'output', 'desc', 'priority'}) do
|
||||
for j, field in ipairs({'condition', 'output', 'desc', 'priority'}) do
|
||||
if (v[field] == nil) then
|
||||
error ("Missing "..field.." in rule with name "..v['rule'])
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Rule must have property "..field)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -356,7 +462,7 @@ function load_rules(sinsp_lua_parser,
|
||||
end
|
||||
end
|
||||
else
|
||||
error ("Unknown rule object: "..table.tostring(v))
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Unknown rule object: "..table.tostring(v))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -393,7 +499,11 @@ function load_rules(sinsp_lua_parser,
|
||||
|
||||
local v = state.macros_by_name[name]
|
||||
|
||||
local ast = compiler.compile_macro(v['condition'], state.macros, state.lists)
|
||||
local status, ast = compiler.compile_macro(v['condition'], state.macros, state.lists)
|
||||
|
||||
if status == false then
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), ast)
|
||||
end
|
||||
|
||||
if v['source'] == "syscall" then
|
||||
if not all_events then
|
||||
@@ -413,8 +523,12 @@ function load_rules(sinsp_lua_parser,
|
||||
warn_evttypes = v['warn_evttypes']
|
||||
end
|
||||
|
||||
local filter_ast, filters = compiler.compile_filter(v['rule'], v['condition'],
|
||||
state.macros, state.lists)
|
||||
local status, filter_ast, filters = compiler.compile_filter(v['rule'], v['condition'],
|
||||
state.macros, state.lists)
|
||||
|
||||
if status == false then
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), filter_ast)
|
||||
end
|
||||
|
||||
local evtttypes = {}
|
||||
local syscallnums = {}
|
||||
@@ -551,7 +665,7 @@ function load_rules(sinsp_lua_parser,
|
||||
formatter = formats.formatter(v['source'], v['output'])
|
||||
formats.free_formatter(v['source'], formatter)
|
||||
else
|
||||
error ("Unexpected type in load_rule: "..filter_ast.type)
|
||||
return false, build_error(lines, indices[i], (indices[i+1]-indices[i]), "Unexpected type in load_rule: "..filter_ast.type)
|
||||
end
|
||||
|
||||
::next_rule::
|
||||
@@ -574,7 +688,7 @@ function load_rules(sinsp_lua_parser,
|
||||
|
||||
io.flush()
|
||||
|
||||
return required_engine_version
|
||||
return true, required_engine_version
|
||||
end
|
||||
|
||||
local rule_fmt = "%-50s %s"
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include "rules.h"
|
||||
#include "logger.h"
|
||||
|
||||
@@ -425,15 +426,30 @@ 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, 1, 0) != 0)
|
||||
if(lua_pcall(m_ls, 9, 2, 0) != 0)
|
||||
{
|
||||
const char* lerr = lua_tostring(m_ls, -1);
|
||||
|
||||
string err = "Error loading rules: " + string(lerr);
|
||||
|
||||
throw falco_exception(err);
|
||||
}
|
||||
|
||||
required_engine_version = lua_tonumber(m_ls, -1);
|
||||
lua_pop(m_ls, 1);
|
||||
// Either returns (true, required_engine_version), or (false, error string)
|
||||
bool successful = lua_toboolean(m_ls, -2);
|
||||
|
||||
if(successful)
|
||||
{
|
||||
required_engine_version = lua_tonumber(m_ls, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string err = lua_tostring(m_ls, -1);
|
||||
throw falco_exception(err);
|
||||
}
|
||||
|
||||
lua_pop(m_ls, 4);
|
||||
|
||||
} else {
|
||||
throw falco_exception("No function " + m_lua_load_rules + " found in lua rule module");
|
||||
}
|
||||
|
||||
@@ -202,19 +202,8 @@ void falco_ruleset::add(string &name,
|
||||
}
|
||||
}
|
||||
|
||||
void falco_ruleset::enable(const string &pattern, bool enabled, uint16_t ruleset)
|
||||
void falco_ruleset::enable(const string &substring, bool enabled, uint16_t ruleset)
|
||||
{
|
||||
regex re;
|
||||
bool match_using_regex = true;
|
||||
|
||||
try {
|
||||
re.assign(pattern);
|
||||
}
|
||||
catch (std::regex_error e)
|
||||
{
|
||||
match_using_regex = false;
|
||||
}
|
||||
|
||||
while (m_rulesets.size() < (size_t) ruleset + 1)
|
||||
{
|
||||
m_rulesets.push_back(new ruleset_filters());
|
||||
@@ -223,14 +212,9 @@ void falco_ruleset::enable(const string &pattern, bool enabled, uint16_t ruleset
|
||||
for(const auto &val : m_filters)
|
||||
{
|
||||
bool matches;
|
||||
if(match_using_regex)
|
||||
{
|
||||
matches = regex_match(val.first, re);
|
||||
}
|
||||
else
|
||||
{
|
||||
matches = (val.first.find(pattern) != string::npos);
|
||||
}
|
||||
|
||||
matches = (substring == "" || (val.first.find(substring) != string::npos));
|
||||
|
||||
if (matches)
|
||||
{
|
||||
if(enabled)
|
||||
|
||||
@@ -24,7 +24,6 @@ limitations under the License.
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
|
||||
#include "sinsp.h"
|
||||
#include "filter.h"
|
||||
@@ -48,9 +47,9 @@ public:
|
||||
// specifying unnecessarily large rulesets will result in
|
||||
// unnecessarily large vectors.
|
||||
|
||||
// Find those rules matching the provided pattern and set
|
||||
// Find those rules matching the provided substring and set
|
||||
// their enabled status to enabled.
|
||||
void enable(const std::string &pattern, bool enabled, uint16_t ruleset = 0);
|
||||
void enable(const std::string &substring, bool enabled, uint16_t ruleset = 0);
|
||||
|
||||
// Find those rules that have a tag in the set of tags and set
|
||||
// their enabled status to enabled. Note that the enabled
|
||||
|
||||
@@ -87,7 +87,7 @@ static void usage()
|
||||
" --cri <path> Path to CRI socket for container metadata\n"
|
||||
" Use the specified socket to fetch data from a CRI-compatible runtime\n"
|
||||
" -d, --daemon Run as a daemon\n"
|
||||
" -D <pattern> Disable any rules matching the regex <pattern>. Can be specified multiple times.\n"
|
||||
" -D <substring> Disable any rules with names having the substring <substring>. Can be specified multiple times.\n"
|
||||
" Can not be specified with -t.\n"
|
||||
" -e <events_file> Read the events from <events_file> (in .scap format for sinsp events, or jsonl for\n"
|
||||
" k8s audit events) instead of tapping into live.\n"
|
||||
@@ -471,9 +471,9 @@ int falco_init(int argc, char **argv)
|
||||
|
||||
try
|
||||
{
|
||||
set<string> disabled_rule_patterns;
|
||||
string pattern;
|
||||
string all_rules = ".*";
|
||||
set<string> disabled_rule_substrings;
|
||||
string substring;
|
||||
string all_rules = "";
|
||||
set<string> disabled_rule_tags;
|
||||
set<string> enabled_rule_tags;
|
||||
|
||||
@@ -502,8 +502,8 @@ int falco_init(int argc, char **argv)
|
||||
daemon = true;
|
||||
break;
|
||||
case 'D':
|
||||
pattern = optarg;
|
||||
disabled_rule_patterns.insert(pattern);
|
||||
substring = optarg;
|
||||
disabled_rule_substrings.insert(substring);
|
||||
break;
|
||||
case 'e':
|
||||
trace_filename = optarg;
|
||||
@@ -716,7 +716,17 @@ int falco_init(int argc, char **argv)
|
||||
}
|
||||
for(auto file : validate_rules_filenames)
|
||||
{
|
||||
engine->load_rules_file(file, verbose, all_events);
|
||||
// Only include the prefix if there is more than one file
|
||||
std::string prefix = (validate_rules_filenames.size() > 1 ? file + ": " : "");
|
||||
try {
|
||||
engine->load_rules_file(file, verbose, all_events);
|
||||
}
|
||||
catch(falco_exception &e)
|
||||
{
|
||||
printf("%s%s\n", prefix.c_str(), e.what());
|
||||
throw;
|
||||
}
|
||||
printf("%sOk\n", prefix.c_str());
|
||||
}
|
||||
falco_logger::log(LOG_INFO, "Ok\n");
|
||||
goto exit;
|
||||
@@ -771,15 +781,15 @@ int falco_init(int argc, char **argv)
|
||||
}
|
||||
|
||||
// You can't both disable and enable rules
|
||||
if((disabled_rule_patterns.size() + disabled_rule_tags.size() > 0) &&
|
||||
if((disabled_rule_substrings.size() + disabled_rule_tags.size() > 0) &&
|
||||
enabled_rule_tags.size() > 0) {
|
||||
throw std::invalid_argument("You can not specify both disabled (-D/-T) and enabled (-t) rules");
|
||||
}
|
||||
|
||||
for (auto pattern : disabled_rule_patterns)
|
||||
for (auto substring : disabled_rule_substrings)
|
||||
{
|
||||
falco_logger::log(LOG_INFO, "Disabling rules matching pattern: " + pattern + "\n");
|
||||
engine->enable_rule(pattern, false);
|
||||
falco_logger::log(LOG_INFO, "Disabling rules matching substring: " + substring + "\n");
|
||||
engine->enable_rule(substring, false);
|
||||
}
|
||||
|
||||
if(disabled_rule_tags.size() > 0)
|
||||
|
||||
Reference in New Issue
Block a user