Compare commits

...

5 Commits

Author SHA1 Message Date
Leonardo Grasso
7ab327749f chore(userspace/engine): format lua source code
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2020-07-29 14:44:55 +02:00
Leonardo Grasso
4450fd3c4c revert(rules): remove require_engine_version at rule level
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2020-07-29 14:44:54 +02:00
Lorenzo Fontana
5cca1a6589 rule(Create Disallowed Pod): required_engine_version 5
rule(Create Privileged Pod): required_engine_version 5
rule(Create Sensitive Mount Pod): required_engine_version 5
rule(Create HostNetwork Pod): required_engine_version 5
rule(Pod Created in Kube Namespace): required_engine_version 5
rule(ClusterRole With Wildcard Created): required_engine_version 5
rule(ClusterRole With Write Privileges Created): required_engine_version 5
rule(ClusterRole With Pod Exec Created): required_engine_version 5

Co-Authored-By: Leonardo Di Donato <leodidonato@gmail.com>
Signed-off-by: Lorenzo Fontana <lo@linux.com>
2020-07-29 14:44:51 +02:00
Lorenzo Fontana
130126f170 rules(Container Drift Detected (open+create)): specify that rule is only
compatible with engine 6

Co-Authored-By: Leonardo Di Donato <leodidonato@gmail.com>
Signed-off-by: Lorenzo Fontana <lo@linux.com>
2020-07-29 14:43:26 +02:00
Lorenzo Fontana
c886debf83 rules: the required_engine_version is now on by default
Co-Authored-By: Leonardo Di Donato <leodidonato@gmail.com>
Signed-off-by: Lorenzo Fontana <lo@linux.com>
2020-07-29 14:42:05 +02:00
7 changed files with 1990 additions and 1410 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -44,8 +44,16 @@
items: ["vpa-recommender", "vpa-updater"] items: ["vpa-recommender", "vpa-updater"]
- list: allowed_k8s_users - list: allowed_k8s_users
items: [ items:
"minikube", "minikube-user", "kubelet", "kops", "admin", "kube", "kube-proxy", "kube-apiserver-healthcheck", [
"minikube",
"minikube-user",
"kubelet",
"kops",
"admin",
"kube",
"kube-proxy",
"kube-apiserver-healthcheck",
"kubernetes-admin", "kubernetes-admin",
vertical_pod_autoscaler_users, vertical_pod_autoscaler_users,
] ]
@@ -115,6 +123,7 @@
- macro: health_endpoint - macro: health_endpoint
condition: ka.uri=/healthz condition: ka.uri=/healthz
# requires FALCO_ENGINE_VERSION 5
- rule: Create Disallowed Pod - rule: Create Disallowed Pod
desc: > desc: >
Detect an attempt to start a pod with a container image outside of a list of allowed images. Detect an attempt to start a pod with a container image outside of a list of allowed images.
@@ -124,6 +133,7 @@
source: k8s_audit source: k8s_audit
tags: [k8s] tags: [k8s]
# requires FALCO_ENGINE_VERSION 5
- rule: Create Privileged Pod - rule: Create Privileged Pod
desc: > desc: >
Detect an attempt to start a pod with a privileged container Detect an attempt to start a pod with a privileged container
@@ -137,6 +147,7 @@
condition: > condition: >
(ka.req.pod.volumes.hostpath intersects (/proc, /var/run/docker.sock, /, /etc, /root, /var/run/crio/crio.sock, /home/admin, /var/lib/kubelet, /var/lib/kubelet/pki, /etc/kubernetes, /etc/kubernetes/manifests)) (ka.req.pod.volumes.hostpath intersects (/proc, /var/run/docker.sock, /, /etc, /root, /var/run/crio/crio.sock, /home/admin, /var/lib/kubelet, /var/lib/kubelet/pki, /etc/kubernetes, /etc/kubernetes/manifests))
# requires FALCO_ENGINE_VERSION 5
- rule: Create Sensitive Mount Pod - rule: Create Sensitive Mount Pod
desc: > desc: >
Detect an attempt to start a pod with a volume from a sensitive host directory (i.e. /proc). Detect an attempt to start a pod with a volume from a sensitive host directory (i.e. /proc).
@@ -148,6 +159,7 @@
tags: [k8s] tags: [k8s]
# Corresponds to K8s CIS Benchmark 1.7.4 # Corresponds to K8s CIS Benchmark 1.7.4
# requires FALCO_ENGINE_VERSION 5
- rule: Create HostNetwork Pod - rule: Create HostNetwork Pod
desc: Detect an attempt to start a pod using the host network. desc: Detect an attempt to start a pod using the host network.
condition: kevt and pod and kcreate and ka.req.pod.host_network intersects (true) and not ka.req.pod.containers.image.repository in (falco_hostnetwork_images) condition: kevt and pod and kcreate and ka.req.pod.host_network intersects (true) and not ka.req.pod.containers.image.repository in (falco_hostnetwork_images)
@@ -236,6 +248,7 @@
condition: (ka.req.pod.containers.image.repository in (user_trusted_image_list)) condition: (ka.req.pod.containers.image.repository in (user_trusted_image_list))
# Detect any new pod created in the kube-system namespace # Detect any new pod created in the kube-system namespace
# requires FALCO_ENGINE_VERSION 5
- rule: Pod Created in Kube Namespace - rule: Pod Created in Kube Namespace
desc: Detect any attempt to create a pod in the kube-system or kube-public namespaces desc: Detect any attempt to create a pod in the kube-system or kube-public namespaces
condition: kevt and pod and kcreate and ka.target.namespace in (kube-system, kube-public) and not trusted_pod condition: kevt and pod and kcreate and ka.target.namespace in (kube-system, kube-public) and not trusted_pod
@@ -280,6 +293,7 @@
source: k8s_audit source: k8s_audit
tags: [k8s] tags: [k8s]
# requires FALCO_ENGINE_VERSION 5
- rule: ClusterRole With Wildcard Created - rule: ClusterRole With Wildcard Created
desc: Detect any attempt to create a Role/ClusterRole with wildcard resources or verbs desc: Detect any attempt to create a Role/ClusterRole with wildcard resources or verbs
condition: kevt and (role or clusterrole) and kcreate and (ka.req.role.rules.resources intersects ("*") or ka.req.role.rules.verbs intersects ("*")) condition: kevt and (role or clusterrole) and kcreate and (ka.req.role.rules.resources intersects ("*") or ka.req.role.rules.verbs intersects ("*"))
@@ -292,6 +306,7 @@
condition: > condition: >
(ka.req.role.rules.verbs intersects (create, update, patch, delete, deletecollection)) (ka.req.role.rules.verbs intersects (create, update, patch, delete, deletecollection))
# requires FALCO_ENGINE_VERSION 5
- rule: ClusterRole With Write Privileges Created - rule: ClusterRole With Write Privileges Created
desc: Detect any attempt to create a Role/ClusterRole that can perform write-related actions desc: Detect any attempt to create a Role/ClusterRole that can perform write-related actions
condition: kevt and (role or clusterrole) and kcreate and writable_verbs condition: kevt and (role or clusterrole) and kcreate and writable_verbs
@@ -300,6 +315,7 @@
source: k8s_audit source: k8s_audit
tags: [k8s] tags: [k8s]
# requires FALCO_ENGINE_VERSION 5
- rule: ClusterRole With Pod Exec Created - rule: ClusterRole With Pod Exec Created
desc: Detect any attempt to create a Role/ClusterRole that can exec to pods desc: Detect any attempt to create a Role/ClusterRole that can exec to pods
condition: kevt and (role or clusterrole) and kcreate and ka.req.role.rules.resources intersects ("pods/exec") condition: kevt and (role or clusterrole) and kcreate and ka.req.role.rules.resources intersects ("pods/exec")
@@ -463,14 +479,20 @@
source: k8s_audit source: k8s_audit
tags: [k8s] tags: [k8s]
# This macro disables following rule, change to k8s_audit_never_true to enable it # This macro disables following rule, change to k8s_audit_never_true to enable it
- macro: allowed_full_admin_users - macro: allowed_full_admin_users
condition: (k8s_audit_always_true) condition: (k8s_audit_always_true)
# This list includes some of the default user names for an administrator in several K8s installations # This list includes some of the default user names for an administrator in several K8s installations
- list: full_admin_k8s_users - list: full_admin_k8s_users
items: ["admin", "kubernetes-admin", "kubernetes-admin@kubernetes", "kubernetes-admin@cluster.local", "minikube-user"] items:
[
"admin",
"kubernetes-admin",
"kubernetes-admin@kubernetes",
"kubernetes-admin@cluster.local",
"minikube-user",
]
# This rules detect an operation triggered by an user name that is # This rules detect an operation triggered by an user name that is
# included in the list of those that are default administrators upon # included in the list of those that are default administrators upon
@@ -572,4 +594,3 @@
priority: WARNING priority: WARNING
source: k8s_audit source: k8s_audit
tags: [k8s] tags: [k8s]

View File

@@ -1,3 +1,2 @@
- macro: allowed_k8s_containers - macro: allowed_k8s_containers
condition: (ka.req.pod.containers.image.repository in (nginx)) condition: (ka.req.pod.containers.image.repository in (nginx))

View File

@@ -1,4 +1,4 @@
-- Copyright (C) 2019 The Falco Authors. -- Copyright (C) 2020 The Falco Authors.
-- --
-- Licensed under the Apache License, Version 2.0 (the "License"); -- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License. -- you may not use this file except in compliance with the License.
@@ -11,7 +11,6 @@
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and -- See the License for the specific language governing permissions and
-- limitations under the License. -- limitations under the License.
local parser = require("parser") local parser = require("parser")
local compiler = {} local compiler = {}
@@ -47,9 +46,13 @@ end
--]] --]]
function copy_ast_obj(obj) function copy_ast_obj(obj)
if type(obj) ~= 'table' then return obj end if type(obj) ~= 'table' then
return obj
end
local res = {} local res = {}
for k, v in pairs(obj) do res[copy_ast_obj(k)] = copy_ast_obj(v) end for k, v in pairs(obj) do
res[copy_ast_obj(k)] = copy_ast_obj(v)
end
return res return res
end end
@@ -127,8 +130,12 @@ function get_macros(ast, set)
local left = get_macros(ast.left, {}) local left = get_macros(ast.left, {})
local right = get_macros(ast.right, {}) local right = get_macros(ast.right, {})
for m, _ in pairs(left) do set[m] = true end for m, _ in pairs(left) do
for m, _ in pairs(right) do set[m] = true end set[m] = true
end
for m, _ in pairs(right) do
set[m] = true
end
return set return set
end end
@@ -148,7 +155,9 @@ function get_filters(ast)
end end
end end
parser.traverse_ast(ast.filter.value, {FieldName=1} , cb) parser.traverse_ast(ast.filter.value, {
FieldName = 1
}, cb)
return filters return filters
end end
@@ -165,7 +174,8 @@ function compiler.expand_lists_in(source, list_defs)
local epos = bpos + string.len(name) local epos = bpos + string.len(name)
-- The characters surrounding the name must be delimiters of beginning/end of string -- The characters surrounding the name must be delimiters of beginning/end of string
if (bpos == 1 or string.match(string.sub(source, bpos-1, bpos-1), "[%s(),=]")) and (epos > string.len(source) or string.match(string.sub(source, epos, epos), "[%s(),=]")) then if (bpos == 1 or string.match(string.sub(source, bpos - 1, bpos - 1), "[%s(),=]")) and
(epos > string.len(source) or string.match(string.sub(source, epos, epos), "[%s(),=]")) then
new_source = "" new_source = ""
if bpos > 1 then if bpos > 1 then
@@ -256,5 +266,4 @@ function compiler.compile_filter(name, source, macro_defs, list_defs)
return true, ast, filters return true, ast, filters
end end
return compiler return compiler

View File

@@ -1,4 +1,4 @@
-- Copyright (C) 2019 The Falco Authors. -- Copyright (C) 2020 The Falco Authors.
-- --
-- Licensed under the Apache License, Version 2.0 (the "License"); -- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License. -- you may not use this file except in compliance with the License.
@@ -12,7 +12,6 @@
-- See the License for the specific language governing permissions and -- See the License for the specific language governing permissions and
-- limitations under the License. -- limitations under the License.
-- --
--[[ --[[
Falco grammar and parser. Falco grammar and parser.
@@ -108,7 +107,10 @@ end
local function list(pat, sep) local function list(pat, sep)
return Ct(pat ^ -1 * (sep * pat ^ 0) ^ 0) / function(elements) return Ct(pat ^ -1 * (sep * pat ^ 0) ^ 0) / function(elements)
return {type = "List", elements = elements} return {
type = "List",
elements = elements
}
end end
end end
@@ -128,23 +130,39 @@ local function terminal(tag)
if tag ~= "String" then if tag ~= "String" then
val = trim(tok) val = trim(tok)
end end
return {type = tag, value = val} return {
type = tag,
value = val
}
end end
end end
local function unaryboolop(op, e) local function unaryboolop(op, e)
return {type = "UnaryBoolOp", operator = op, argument = e} return {
type = "UnaryBoolOp",
operator = op,
argument = e
}
end end
local function unaryrelop(e, op) local function unaryrelop(e, op)
return {type = "UnaryRelOp", operator = op, argument = e} return {
type = "UnaryRelOp",
operator = op,
argument = e
}
end end
local function binaryop(e1, op, e2) local function binaryop(e1, op, e2)
if not op then if not op then
return e1 return e1
else else
return {type = "BinaryBoolOp", operator = op, left = e1, right = e2} return {
type = "BinaryBoolOp",
operator = op,
left = e1,
right = e2
}
end end
end end
@@ -154,18 +172,29 @@ end
local function rel(left, sep, right) local function rel(left, sep, right)
return left * sep * right / function(e1, op, e2) return left * sep * right / function(e1, op, e2)
return {type = "BinaryRelOp", operator = op, left = e1, right = e2} return {
type = "BinaryRelOp",
operator = op,
left = e1,
right = e2
}
end end
end end
-- grammar -- grammar
local function filter(e) local function filter(e)
return {type = "Filter", value = e} return {
type = "Filter",
value = e
}
end end
local function rule(filter) local function rule(filter)
return {type = "Rule", filter = filter} return {
type = "Rule",
filter = filter
}
end end
local G = { local G = {
@@ -181,8 +210,7 @@ local G = {
ExistsExpression = terminal "FieldName" * V "ExistsOp" / unaryrelop + V "MacroExpression", ExistsExpression = terminal "FieldName" * V "ExistsOp" / unaryrelop + V "MacroExpression",
MacroExpression = terminal "Macro" + V "RelationalExpression", MacroExpression = terminal "Macro" + V "RelationalExpression",
RelationalExpression = rel(terminal "FieldName", V "RelOp", V "Value") + RelationalExpression = rel(terminal "FieldName", V "RelOp", V "Value") +
rel(terminal "FieldName", V "SetOp", V "InList") + rel(terminal "FieldName", V "SetOp", V "InList") + V "PrimaryExp",
V "PrimaryExp",
PrimaryExp = symb("(") * V "Filter" * symb(")"), PrimaryExp = symb("(") * V "Filter" * symb(")"),
FuncArgs = symb("(") * list(V "Value", symb(",")) * symb(")"), FuncArgs = symb("(") * list(V "Value", symb(",")) * symb(")"),
-- Terminals -- Terminals
@@ -207,20 +235,15 @@ local G = {
Number = C(V "Hex" + V "Float" + V "Int") / function(n) Number = C(V "Hex" + V "Float" + V "Int") / function(n)
return tonumber(n) return tonumber(n)
end, end,
String = (P '"' * C(((P "\\" * P(1)) + (P(1) - P '"')) ^ 0) * P '"' + String = (P '"' * C(((P "\\" * P(1)) + (P(1) - P '"')) ^ 0) * P '"' + P "'" *
P "'" * C(((P "\\" * P(1)) + (P(1) - P "'")) ^ 0) * P "'"), C(((P "\\" * P(1)) + (P(1) - P "'")) ^ 0) * P "'"),
BareString = C((P(1) - S " (),=") ^ 1), BareString = C((P(1) - S " (),=") ^ 1),
OrOp = kw("or") / "or", OrOp = kw("or") / "or",
AndOp = kw("and") / "and", AndOp = kw("and") / "and",
Colon = kw(":"), Colon = kw(":"),
RelOp = symb("=") / "=" + symb("==") / "==" + symb("!=") / "!=" + symb("<=") / "<=" + symb(">=") / ">=" + RelOp = symb("=") / "=" + symb("==") / "==" + symb("!=") / "!=" + symb("<=") / "<=" + symb(">=") / ">=" + symb("<") /
symb("<") / "<" + "<" + symb(">") / ">" + symb("contains") / "contains" + symb("icontains") / "icontains" + symb("glob") / "glob" +
symb(">") / ">" + symb("startswith") / "startswith" + symb("endswith") / "endswith",
symb("contains") / "contains" +
symb("icontains") / "icontains" +
symb("glob") / "glob" +
symb("startswith") / "startswith" +
symb("endswith") / "endswith",
SetOp = kw("in") / "in" + kw("intersects") / "intersects" + kw("pmatch") / "pmatch", SetOp = kw("in") / "in" + kw("intersects") / "intersects" + kw("pmatch") / "pmatch",
UnaryBoolOp = kw("not") / "not", UnaryBoolOp = kw("not") / "not",
ExistsOp = kw("exists") / "exists", ExistsOp = kw("exists") / "exists",
@@ -232,7 +255,9 @@ local G = {
Parses a single filter and returns the AST. Parses a single filter and returns the AST.
--]] --]]
function parser.parse_filter(subject) function parser.parse_filter(subject)
local errorinfo = {subject = subject} local errorinfo = {
subject = subject
}
lpeg.setmaxstack(1000) lpeg.setmaxstack(1000)
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo) local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
return ast, error_msg return ast, error_msg

View File

@@ -1,4 +1,4 @@
-- Copyright (C) 2019 The Falco Authors. -- Copyright (C) 2020 The Falco Authors.
-- --
-- Licensed under the Apache License, Version 2.0 (the "License"); -- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License. -- you may not use this file except in compliance with the License.
@@ -12,19 +12,16 @@
-- See the License for the specific language governing permissions and -- See the License for the specific language governing permissions and
-- limitations under the License. -- limitations under the License.
-- --
--[[ --[[
Compile and install falco rules. Compile and install falco rules.
This module exports functions that are called from falco c++-side to compile and install a set of rules. This module exports functions that are called from falco c++-side to compile and install a set of rules.
--]] --]]
local sinsp_rule_utils = require "sinsp_rule_utils" local sinsp_rule_utils = require "sinsp_rule_utils"
local compiler = require "compiler" local compiler = require "compiler"
local yaml = require "lyaml" local yaml = require "lyaml"
--[[ --[[
Traverse AST, adding the passed-in 'index' to each node that contains a relational expression Traverse AST, adding the passed-in 'index' to each node that contains a relational expression
--]] --]]
@@ -57,13 +54,34 @@ function map(f, arr)
return res return res
end end
-- Permissive for case and for common abbreviations. -- Permissive for case and for common abbreviations.
priorities = { priorities = {
Emergency=0, Alert=1, Critical=2, Error=3, Warning=4, Notice=5, Informational=5, Debug=7, Emergency = 0,
emergency=0, alert=1, critical=2, error=3, warning=4, notice=5, informational=5, debug=7, Alert = 1,
EMERGENCY=0, ALERT=1, CRITICAL=2, ERROR=3, WARNING=4, NOTICE=5, INFORMATIONAL=5, DEBUG=7, Critical = 2,
INFO=5, info=5 Error = 3,
Warning = 4,
Notice = 5,
Informational = 5,
Debug = 7,
emergency = 0,
alert = 1,
critical = 2,
error = 3,
warning = 4,
notice = 5,
informational = 5,
debug = 7,
EMERGENCY = 0,
ALERT = 1,
CRITICAL = 2,
ERROR = 3,
WARNING = 4,
NOTICE = 5,
INFORMATIONAL = 5,
DEBUG = 7,
INFO = 5,
info = 5
} }
--[[ --[[
@@ -96,10 +114,10 @@ local function install_filter(node, filter_api_lib, lua_parser, parent_bool_op)
filter_api_lib.unnest(lua_parser) -- io.write(")") filter_api_lib.unnest(lua_parser) -- io.write(")")
elseif t == "BinaryRelOp" then elseif t == "BinaryRelOp" then
if (node.operator == "in" or if (node.operator == "in" or node.operator == "intersects" or node.operator == "pmatch") then
node.operator == "intersects" or elements = map(function(el)
node.operator == "pmatch") then return el.value
elements = map(function (el) return el.value end, node.right.elements) end, node.right.elements)
filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, elements, node.index) filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, elements, node.index)
else else
filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, node.right.value, node.index) filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, node.right.value, node.index)
@@ -130,9 +148,20 @@ end
-- object. The by_name index is used for things like describing rules, -- object. The by_name index is used for things like describing rules,
-- and the by_idx index is used to map the relational node index back -- and the by_idx index is used to map the relational node index back
-- to a rule. -- to a rule.
local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, local state = {
skipped_rules_by_name={}, macros_by_name={}, lists_by_name={}, macros = {},
n_rules=0, rules_by_idx={}, ordered_rule_names={}, ordered_macro_names={}, ordered_list_names={}} lists = {},
filter_ast = nil,
rules_by_name = {},
skipped_rules_by_name = {},
macros_by_name = {},
lists_by_name = {},
n_rules = 0,
rules_by_idx = {},
ordered_rule_names = {},
ordered_macro_names = {},
ordered_list_names = {}
}
local function reset_rules(rules_mgr) local function reset_rules(rules_mgr)
falco_rules.clear_filters(rules_mgr) falco_rules.clear_filters(rules_mgr)
@@ -152,8 +181,7 @@ function table.val_to_str ( v )
end end
return '"' .. string.gsub(v, '"', '\\"') .. '"' return '"' .. string.gsub(v, '"', '\\"') .. '"'
else else
return "table" == type( v ) and table.tostring( v ) or return "table" == type(v) and table.tostring(v) or tostring(v)
tostring( v )
end end
end end
@@ -173,8 +201,7 @@ function table.tostring( tbl )
end end
for k, v in pairs(tbl) do for k, v in pairs(tbl) do
if not done[k] then if not done[k] then
table.insert( result, table.insert(result, table.key_to_str(k) .. "=" .. table.val_to_str(v))
table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
end end
end end
return "{" .. table.concat(result, ",") .. "}" return "{" .. table.concat(result, ",") .. "}"
@@ -275,11 +302,11 @@ function load_rules_doc(rules_mgr, doc, load_state)
load_state.cur_item_idx = load_state.cur_item_idx + 1 load_state.cur_item_idx = load_state.cur_item_idx + 1
-- Save back the original object as it appeared in the file. Will be used to provide context. -- Save back the original object as it appeared in the file. Will be used to provide context.
local context = get_orig_yaml_obj(load_state.lines, local context = get_orig_yaml_obj(load_state.lines, load_state.indices[load_state.cur_item_idx])
load_state.indices[load_state.cur_item_idx])
if (not (type(v) == "table")) then if (not (type(v) == "table")) then
return false, build_error_with_context(context, "Unexpected element of type " ..type(v)..". Each element should be a yaml associative array.") return false, build_error_with_context(context, "Unexpected element of type " .. type(v) ..
". Each element should be a yaml associative array.")
end end
v['context'] = context v['context'] = context
@@ -287,11 +314,14 @@ function load_rules_doc(rules_mgr, doc, load_state)
if (v['required_engine_version']) then if (v['required_engine_version']) then
load_state.required_engine_version = v['required_engine_version'] load_state.required_engine_version = v['required_engine_version']
if type(load_state.required_engine_version) ~= "number" then if type(load_state.required_engine_version) ~= "number" then
return false, build_error_with_context(v['context'], "Value of required_engine_version must be a number") return false,
build_error_with_context(v['context'], "Value of required_engine_version must be a number")
end end
if falco_rules.engine_version(rules_mgr) < v['required_engine_version'] then if falco_rules.engine_version(rules_mgr) < v['required_engine_version'] then
return false, build_error_with_context(v['context'], "Rules require engine version "..v['required_engine_version']..", but engine version is "..falco_rules.engine_version(rules_mgr)) return false, build_error_with_context(v['context'],
"Rules require engine version " .. v['required_engine_version'] .. ", but engine version is " ..
falco_rules.engine_version(rules_mgr))
end end
elseif (v['macro']) then elseif (v['macro']) then
@@ -323,13 +353,16 @@ function load_rules_doc(rules_mgr, doc, load_state)
if append then if append then
if state.macros_by_name[v['macro']] == nil then if state.macros_by_name[v['macro']] == nil then
return false, build_error_with_context(v['context'], "Macro " ..v['macro'].. " has 'append' key but no macro by that name already exists") return false, build_error_with_context(v['context'], "Macro " .. v['macro'] ..
" has 'append' key but no macro by that name already exists")
end end
state.macros_by_name[v['macro']]['condition'] = state.macros_by_name[v['macro']]['condition'] .. " " .. v['condition'] state.macros_by_name[v['macro']]['condition'] =
state.macros_by_name[v['macro']]['condition'] .. " " .. v['condition']
-- Add the current object to the context of the base macro -- Add the current object to the context of the base macro
state.macros_by_name[v['macro']]['context'] = state.macros_by_name[v['macro']]['context'].."\n"..v['context'] state.macros_by_name[v['macro']]['context'] =
state.macros_by_name[v['macro']]['context'] .. "\n" .. v['context']
else else
state.macros_by_name[v['macro']] = v state.macros_by_name[v['macro']] = v
@@ -360,7 +393,8 @@ function load_rules_doc(rules_mgr, doc, load_state)
if append then if append then
if state.lists_by_name[v['list']] == nil then if state.lists_by_name[v['list']] == nil then
return false, build_error_with_context(v['context'], "List " ..v['list'].. " has 'append' key but no list by that name already exists") return false, build_error_with_context(v['context'], "List " .. v['list'] ..
" has 'append' key but no list by that name already exists")
end end
for j, elem in ipairs(v['items']) do for j, elem in ipairs(v['items']) do
@@ -404,13 +438,16 @@ function load_rules_doc(rules_mgr, doc, load_state)
if state.rules_by_name[v['rule']] == nil then if state.rules_by_name[v['rule']] == nil then
if state.skipped_rules_by_name[v['rule']] == nil then if state.skipped_rules_by_name[v['rule']] == nil then
return false, build_error_with_context(v['context'], "Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists") return false, build_error_with_context(v['context'], "Rule " .. v['rule'] ..
" has 'append' key but no rule by that name already exists")
end end
else else
state.rules_by_name[v['rule']]['condition'] = state.rules_by_name[v['rule']]['condition'] .. " " .. v['condition'] state.rules_by_name[v['rule']]['condition'] =
state.rules_by_name[v['rule']]['condition'] .. " " .. v['condition']
-- Add the current object to the context of the base rule -- Add the current object to the context of the base rule
state.rules_by_name[v['rule']]['context'] = state.rules_by_name[v['rule']]['context'].."\n"..v['context'] state.rules_by_name[v['rule']]['context'] =
state.rules_by_name[v['rule']]['context'] .. "\n" .. v['context']
end end
else else
@@ -456,21 +493,22 @@ function load_rules_doc(rules_mgr, doc, load_state)
return true, "" return true, ""
end end
function load_rules(sinsp_lua_parser, function load_rules(sinsp_lua_parser, json_lua_parser, rules_content, rules_mgr, verbose, all_events, extra,
json_lua_parser, replace_container_info, min_priority)
rules_content,
rules_mgr,
verbose,
all_events,
extra,
replace_container_info,
min_priority)
local load_state = {lines={}, indices={}, cur_item_idx=0, min_priority=min_priority, required_engine_version=0} local load_state = {
lines = {},
indices = {},
cur_item_idx = 0,
min_priority = min_priority,
required_engine_version = 0
}
load_state.lines, load_state.indices = split_lines(rules_content) load_state.lines, load_state.indices = split_lines(rules_content)
local status, docs = pcall(yaml.load, rules_content, { all = true }) local status, docs = pcall(yaml.load, rules_content, {
all = true
})
if status == false then if status == false then
local pat = "^([%d]+):([%d]+): " local pat = "^([%d]+):([%d]+): "
@@ -546,7 +584,10 @@ function load_rules(sinsp_lua_parser,
end end
end end
state.lists[v['list']] = {["items"] = items, ["used"] = false} state.lists[v['list']] = {
["items"] = items,
["used"] = false
}
end end
for _, name in ipairs(state.ordered_macro_names) do for _, name in ipairs(state.ordered_macro_names) do
@@ -565,7 +606,10 @@ function load_rules(sinsp_lua_parser,
end end
end end
state.macros[v['macro']] = {["ast"] = ast.filter.value, ["used"] = false} state.macros[v['macro']] = {
["ast"] = ast.filter.value,
["used"] = false
}
end end
for _, name in ipairs(state.ordered_rule_names) do for _, name in ipairs(state.ordered_rule_names) do
@@ -577,8 +621,8 @@ function load_rules(sinsp_lua_parser,
warn_evttypes = v['warn_evttypes'] warn_evttypes = v['warn_evttypes']
end end
local status, filter_ast, filters = compiler.compile_filter(v['rule'], v['condition'], local status, filter_ast, filters =
state.macros, state.lists) compiler.compile_filter(v['rule'], v['condition'], state.macros, state.lists)
if status == false then if status == false then
return false, build_error_with_context(v['context'], filter_ast) return false, build_error_with_context(v['context'], filter_ast)
@@ -592,7 +636,8 @@ function load_rules(sinsp_lua_parser,
sinsp_rule_utils.check_for_ignored_syscalls_events(filter_ast, 'rule', v['rule']) sinsp_rule_utils.check_for_ignored_syscalls_events(filter_ast, 'rule', v['rule'])
end end
evttypes, syscallnums = sinsp_rule_utils.get_evttypes_syscalls(name, filter_ast, v['condition'], warn_evttypes, verbose) evttypes, syscallnums = sinsp_rule_utils.get_evttypes_syscalls(name, filter_ast, v['condition'],
warn_evttypes, verbose)
end end
-- If a filter in the rule doesn't exist, either skip the rule -- If a filter in the rule doesn't exist, either skip the rule
@@ -673,7 +718,12 @@ function load_rules(sinsp_lua_parser,
if (state.filter_ast == nil) then if (state.filter_ast == nil) then
state.filter_ast = filter_ast.filter.value state.filter_ast = filter_ast.filter.value
else else
state.filter_ast = { type = "BinaryBoolOp", operator = "or", left = state.filter_ast, right = filter_ast.filter.value } state.filter_ast = {
type = "BinaryBoolOp",
operator = "or",
left = state.filter_ast,
right = filter_ast.filter.value
}
end end
-- Enable/disable the rule -- Enable/disable the rule
@@ -697,7 +747,8 @@ function load_rules(sinsp_lua_parser,
-- to replace it, in which case we use the generic -- to replace it, in which case we use the generic
-- "%container.name (id=%container.id)" -- "%container.name (id=%container.id)"
if replace_container_info == false then if replace_container_info == false then
v['output'] = string.gsub(v['output'], "%%container.info", "%%container.name (id=%%container.id)") v['output'] = string.gsub(v['output'], "%%container.info",
"%%container.name (id=%%container.id)")
if extra ~= "" then if extra ~= "" then
v['output'] = v['output'] .. " " .. extra v['output'] = v['output'] .. " " .. extra
end end
@@ -752,8 +803,7 @@ local function wrap(str, limit, indent)
indent = indent or "" indent = indent or ""
limit = limit or 72 limit = limit or 72
local here = 1 local here = 1
return str:gsub("(%s+)()(%S+)()", return str:gsub("(%s+)()(%S+)()", function(sp, st, word, fi)
function(sp, st, word, fi)
if fi - here > limit then if fi - here > limit then
here = st here = st
return "\n" .. indent .. word return "\n" .. indent .. word
@@ -793,7 +843,11 @@ function describe_rule(name)
end end
end end
local rule_output_counts = {total=0, by_priority={}, by_name={}} local rule_output_counts = {
total = 0,
by_priority = {},
by_name = {}
}
function on_event(rule_id) function on_event(rule_id)
@@ -835,5 +889,3 @@ function print_stats()
end end
end end

View File

@@ -1,4 +1,4 @@
-- Copyright (C) 2019 The Falco Authors. -- Copyright (C) 2020 The Falco Authors.
-- --
-- Licensed under the Apache License, Version 2.0 (the "License"); -- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License. -- you may not use this file except in compliance with the License.
@@ -12,7 +12,6 @@
-- See the License for the specific language governing permissions and -- See the License for the specific language governing permissions and
-- limitations under the License. -- limitations under the License.
-- --
local parser = require("parser") local parser = require("parser")
local sinsp_rule_utils = {} local sinsp_rule_utils = {}
@@ -32,13 +31,9 @@ function sinsp_rule_utils.check_for_ignored_syscalls_events(ast, filter_type, so
end end
function cb(node) function cb(node)
if node.left.type == "FieldName" and if node.left.type == "FieldName" and (node.left.value == "evt.type" or node.left.value == "syscall.type") then
(node.left.value == "evt.type" or
node.left.value == "syscall.type") then
if (node.operator == "in" or if (node.operator == "in" or node.operator == "intersects" or node.operator == "pmatch") then
node.operator == "intersects" or
node.operator == "pmatch") then
for i, v in ipairs(node.right.elements) do for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then if v.type == "BareString" then
if node.left.value == "evt.type" then if node.left.value == "evt.type" then
@@ -60,7 +55,9 @@ function sinsp_rule_utils.check_for_ignored_syscalls_events(ast, filter_type, so
end end
end end
parser.traverse_ast(ast, {BinaryRelOp=1}, cb) parser.traverse_ast(ast, {
BinaryRelOp = 1
}, cb)
end end
-- Examine the ast and find the event types/syscalls for which the -- Examine the ast and find the event types/syscalls for which the
@@ -96,9 +93,7 @@ function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes
if found_not then if found_not then
found_event_after_not = true found_event_after_not = true
end end
if (node.operator == "in" or if (node.operator == "in" or node.operator == "intersects" or node.operator == "pmatch") then
node.operator == "intersects" or
node.operator == "pmatch") then
for i, v in ipairs(node.right.elements) do for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then if v.type == "BareString" then
@@ -147,14 +142,19 @@ function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes
end end
end end
parser.traverse_ast(ast.filter.value, {BinaryRelOp=1, UnaryBoolOp=1} , cb) parser.traverse_ast(ast.filter.value, {
BinaryRelOp = 1,
UnaryBoolOp = 1
}, cb)
if not found_event then if not found_event then
if warn_evttypes == true then if warn_evttypes == true then
io.stderr:write("Rule " .. name .. ": warning (no-evttype):\n") io.stderr:write("Rule " .. name .. ": warning (no-evttype):\n")
io.stderr:write(source .. "\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(
io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n") " 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 end
evttypes = {} evttypes = {}
syscallnums = {} syscallnums = {}
@@ -167,7 +167,8 @@ function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes
io.stderr:write(source .. "\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(" 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(" 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(
" 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(" 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") io.stderr:write(" replacing negative matches with positive matches if possible.\n")
end end