mirror of
https://github.com/falcosecurity/falco.git
synced 2025-09-12 21:16:33 +00:00
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>
This commit is contained in:
@@ -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 = {}
|
||||
@@ -558,9 +569,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
|
||||
@@ -771,9 +782,7 @@ end
|
||||
-- - required engine version. 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,
|
||||
@@ -883,12 +892,6 @@ function load_rules(sinsp_lua_parser,
|
||||
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
|
||||
end
|
||||
|
||||
state.macros[v['macro']] = {["ast"] = ast.filter.value, ["used"] = false}
|
||||
end
|
||||
|
||||
@@ -933,41 +936,13 @@ 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'],
|
||||
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
|
||||
end
|
||||
|
||||
if (filter_ast.type == "Rule") then
|
||||
state.n_rules = state.n_rules + 1
|
||||
|
||||
@@ -983,15 +958,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, 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,10 +1032,8 @@ 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
|
||||
local err = falco_rules.is_format_valid(rules_mgr, v['source'], v['output'])
|
||||
if err ~= nil then
|
||||
return false, nil, build_error_with_context(v['context'], err), warnings
|
||||
end
|
||||
else
|
||||
|
@@ -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
|
Reference in New Issue
Block a user