mirror of
https://github.com/falcosecurity/falco.git
synced 2026-03-30 08:32:17 +00:00
Compare commits
5 Commits
embed-lua-
...
fix/1272
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ab327749f | ||
|
|
4450fd3c4c | ||
|
|
5cca1a6589 | ||
|
|
130126f170 | ||
|
|
c886debf83 |
File diff suppressed because it is too large
Load Diff
@@ -44,10 +44,18 @@
|
|||||||
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",
|
[
|
||||||
"kubernetes-admin",
|
"minikube",
|
||||||
vertical_pod_autoscaler_users,
|
"minikube-user",
|
||||||
|
"kubelet",
|
||||||
|
"kops",
|
||||||
|
"admin",
|
||||||
|
"kube",
|
||||||
|
"kube-proxy",
|
||||||
|
"kube-apiserver-healthcheck",
|
||||||
|
"kubernetes-admin",
|
||||||
|
vertical_pod_autoscaler_users,
|
||||||
]
|
]
|
||||||
|
|
||||||
- rule: Disallowed K8s User
|
- rule: Disallowed K8s User
|
||||||
@@ -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
|
||||||
@@ -136,7 +146,8 @@
|
|||||||
- macro: sensitive_vol_mount
|
- macro: sensitive_vol_mount
|
||||||
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)
|
||||||
@@ -179,7 +191,7 @@
|
|||||||
|
|
||||||
- rule: Create/Modify Configmap With Private Credentials
|
- rule: Create/Modify Configmap With Private Credentials
|
||||||
desc: >
|
desc: >
|
||||||
Detect creating/modifying a configmap containing a private credential (aws key, password, etc.)
|
Detect creating/modifying a configmap containing a private credential (aws key, password, etc.)
|
||||||
condition: kevt and configmap and kmodify and contains_private_credentials
|
condition: kevt and configmap and kmodify and contains_private_credentials
|
||||||
output: K8s configmap with private credential (user=%ka.user.name verb=%ka.verb configmap=%ka.req.configmap.name config=%ka.req.configmap.obj)
|
output: K8s configmap with private credential (user=%ka.user.name verb=%ka.verb configmap=%ka.req.configmap.name config=%ka.req.configmap.obj)
|
||||||
priority: WARNING
|
priority: WARNING
|
||||||
@@ -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,20 +479,26 @@
|
|||||||
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
|
||||||
# cluster creation. This may signify a permission setting too broader.
|
# cluster creation. This may signify a permission setting too broader.
|
||||||
# As we can't check for role of the user on a general ka.* event, this
|
# As we can't check for role of the user on a general ka.* event, this
|
||||||
# may or may not be an administrator. Customize the full_admin_k8s_users
|
# may or may not be an administrator. Customize the full_admin_k8s_users
|
||||||
# list to your needs, and activate at your discrection.
|
# list to your needs, and activate at your discrection.
|
||||||
|
|
||||||
# # How to test:
|
# # How to test:
|
||||||
@@ -526,7 +548,7 @@
|
|||||||
output: >
|
output: >
|
||||||
K8s Ingress Without TLS Cert Created (user=%ka.user.name ingress=%ka.target.name
|
K8s Ingress Without TLS Cert Created (user=%ka.user.name ingress=%ka.target.name
|
||||||
namespace=%ka.target.namespace)
|
namespace=%ka.target.namespace)
|
||||||
source: k8s_audit
|
source: k8s_audit
|
||||||
priority: WARNING
|
priority: WARNING
|
||||||
tags: [k8s, network]
|
tags: [k8s, network]
|
||||||
|
|
||||||
@@ -572,4 +594,3 @@
|
|||||||
priority: WARNING
|
priority: WARNING
|
||||||
source: k8s_audit
|
source: k8s_audit
|
||||||
tags: [k8s]
|
tags: [k8s]
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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,25 +11,24 @@
|
|||||||
-- 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 = {}
|
||||||
|
|
||||||
compiler.trim = parser.trim
|
compiler.trim = parser.trim
|
||||||
|
|
||||||
function map(f, arr)
|
function map(f, arr)
|
||||||
local res = {}
|
local res = {}
|
||||||
for i,v in ipairs(arr) do
|
for i, v in ipairs(arr) do
|
||||||
res[i] = f(v)
|
res[i] = f(v)
|
||||||
end
|
end
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
function foldr(f, acc, arr)
|
function foldr(f, acc, arr)
|
||||||
for i,v in pairs(arr) do
|
for i, v in pairs(arr) do
|
||||||
acc = f(acc, v)
|
acc = f(acc, v)
|
||||||
end
|
end
|
||||||
return acc
|
return acc
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -47,181 +46,192 @@ end
|
|||||||
--]]
|
--]]
|
||||||
|
|
||||||
function copy_ast_obj(obj)
|
function copy_ast_obj(obj)
|
||||||
if type(obj) ~= 'table' then return obj end
|
if type(obj) ~= 'table' then
|
||||||
local res = {}
|
return obj
|
||||||
for k, v in pairs(obj) do res[copy_ast_obj(k)] = copy_ast_obj(v) end
|
end
|
||||||
return res
|
local res = {}
|
||||||
|
for k, v in pairs(obj) do
|
||||||
|
res[copy_ast_obj(k)] = copy_ast_obj(v)
|
||||||
|
end
|
||||||
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
function expand_macros(ast, defs, changed)
|
function expand_macros(ast, defs, changed)
|
||||||
|
|
||||||
if (ast.type == "Rule") then
|
if (ast.type == "Rule") then
|
||||||
return expand_macros(ast.filter, defs, changed)
|
return expand_macros(ast.filter, defs, changed)
|
||||||
elseif ast.type == "Filter" then
|
elseif ast.type == "Filter" then
|
||||||
if (ast.value.type == "Macro") then
|
if (ast.value.type == "Macro") then
|
||||||
if (defs[ast.value.value] == nil) then
|
if (defs[ast.value.value] == nil) then
|
||||||
return false, "Undefined macro '".. ast.value.value .. "' used in filter."
|
return false, "Undefined macro '" .. ast.value.value .. "' used in filter."
|
||||||
end
|
end
|
||||||
defs[ast.value.value].used = true
|
defs[ast.value.value].used = true
|
||||||
ast.value = copy_ast_obj(defs[ast.value.value].ast)
|
ast.value = copy_ast_obj(defs[ast.value.value].ast)
|
||||||
changed = true
|
changed = true
|
||||||
return true, changed
|
return true, changed
|
||||||
end
|
end
|
||||||
return expand_macros(ast.value, defs, changed)
|
return expand_macros(ast.value, defs, changed)
|
||||||
|
|
||||||
elseif ast.type == "BinaryBoolOp" then
|
elseif ast.type == "BinaryBoolOp" then
|
||||||
|
|
||||||
if (ast.left.type == "Macro") then
|
if (ast.left.type == "Macro") then
|
||||||
if (defs[ast.left.value] == nil) then
|
if (defs[ast.left.value] == nil) then
|
||||||
return false, "Undefined macro '".. ast.left.value .. "' used in filter."
|
return false, "Undefined macro '" .. ast.left.value .. "' used in filter."
|
||||||
end
|
end
|
||||||
defs[ast.left.value].used = true
|
defs[ast.left.value].used = true
|
||||||
ast.left = copy_ast_obj(defs[ast.left.value].ast)
|
ast.left = copy_ast_obj(defs[ast.left.value].ast)
|
||||||
changed = true
|
changed = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if (ast.right.type == "Macro") then
|
if (ast.right.type == "Macro") then
|
||||||
if (defs[ast.right.value] == nil) then
|
if (defs[ast.right.value] == nil) then
|
||||||
return false, "Undefined macro ".. ast.right.value .. " used in filter."
|
return false, "Undefined macro " .. ast.right.value .. " used in filter."
|
||||||
end
|
end
|
||||||
defs[ast.right.value].used = true
|
defs[ast.right.value].used = true
|
||||||
ast.right = copy_ast_obj(defs[ast.right.value].ast)
|
ast.right = copy_ast_obj(defs[ast.right.value].ast)
|
||||||
changed = true
|
changed = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local status, changed_left = expand_macros(ast.left, defs, false)
|
local status, changed_left = expand_macros(ast.left, defs, false)
|
||||||
if status == false then
|
if status == false then
|
||||||
return false, changed_left
|
return false, changed_left
|
||||||
end
|
end
|
||||||
local status, changed_right = expand_macros(ast.right, defs, false)
|
local status, changed_right = expand_macros(ast.right, defs, false)
|
||||||
if status == false then
|
if status == false then
|
||||||
return false, changed_right
|
return false, changed_right
|
||||||
end
|
end
|
||||||
return true, changed or changed_left or changed_right
|
return true, changed or changed_left or changed_right
|
||||||
|
|
||||||
elseif ast.type == "UnaryBoolOp" then
|
elseif ast.type == "UnaryBoolOp" then
|
||||||
if (ast.argument.type == "Macro") then
|
if (ast.argument.type == "Macro") then
|
||||||
if (defs[ast.argument.value] == nil) then
|
if (defs[ast.argument.value] == nil) then
|
||||||
return false, "Undefined macro ".. ast.argument.value .. " used in filter."
|
return false, "Undefined macro " .. ast.argument.value .. " used in filter."
|
||||||
end
|
end
|
||||||
defs[ast.argument.value].used = true
|
defs[ast.argument.value].used = true
|
||||||
ast.argument = copy_ast_obj(defs[ast.argument.value].ast)
|
ast.argument = copy_ast_obj(defs[ast.argument.value].ast)
|
||||||
changed = true
|
changed = true
|
||||||
end
|
end
|
||||||
return expand_macros(ast.argument, defs, changed)
|
return expand_macros(ast.argument, defs, changed)
|
||||||
end
|
end
|
||||||
return true, changed
|
return true, changed
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_macros(ast, set)
|
function get_macros(ast, set)
|
||||||
if (ast.type == "Macro") then
|
if (ast.type == "Macro") then
|
||||||
set[ast.value] = true
|
set[ast.value] = true
|
||||||
return set
|
return set
|
||||||
end
|
end
|
||||||
|
|
||||||
if ast.type == "Filter" then
|
if ast.type == "Filter" then
|
||||||
return get_macros(ast.value, set)
|
return get_macros(ast.value, set)
|
||||||
end
|
end
|
||||||
|
|
||||||
if ast.type == "BinaryBoolOp" then
|
if ast.type == "BinaryBoolOp" then
|
||||||
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
|
||||||
if ast.type == "UnaryBoolOp" then
|
if ast.type == "UnaryBoolOp" then
|
||||||
return get_macros(ast.argument, set)
|
return get_macros(ast.argument, set)
|
||||||
end
|
end
|
||||||
return set
|
return set
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_filters(ast)
|
function get_filters(ast)
|
||||||
|
|
||||||
local filters = {}
|
local filters = {}
|
||||||
|
|
||||||
function cb(node)
|
function cb(node)
|
||||||
if node.type == "FieldName" then
|
if node.type == "FieldName" then
|
||||||
filters[node.value] = 1
|
filters[node.value] = 1
|
||||||
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
|
||||||
|
|
||||||
function compiler.expand_lists_in(source, list_defs)
|
function compiler.expand_lists_in(source, list_defs)
|
||||||
|
|
||||||
for name, def in pairs(list_defs) do
|
for name, def in pairs(list_defs) do
|
||||||
|
|
||||||
local bpos = string.find(source, name, 1, true)
|
local bpos = string.find(source, name, 1, true)
|
||||||
|
|
||||||
while bpos ~= nil do
|
while bpos ~= nil do
|
||||||
def.used = true
|
def.used = true
|
||||||
|
|
||||||
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
|
||||||
new_source = ""
|
(epos > string.len(source) or string.match(string.sub(source, epos, epos), "[%s(),=]")) then
|
||||||
|
new_source = ""
|
||||||
|
|
||||||
if bpos > 1 then
|
if bpos > 1 then
|
||||||
new_source = new_source..string.sub(source, 1, bpos-1)
|
new_source = new_source .. string.sub(source, 1, bpos - 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
sub = table.concat(def.items, ", ")
|
sub = table.concat(def.items, ", ")
|
||||||
|
|
||||||
new_source = new_source..sub
|
new_source = new_source .. sub
|
||||||
|
|
||||||
if epos <= string.len(source) then
|
if epos <= string.len(source) then
|
||||||
new_source = new_source..string.sub(source, epos, string.len(source))
|
new_source = new_source .. string.sub(source, epos, string.len(source))
|
||||||
end
|
end
|
||||||
|
|
||||||
source = new_source
|
source = new_source
|
||||||
bpos = bpos + (string.len(sub)-string.len(name))
|
bpos = bpos + (string.len(sub) - string.len(name))
|
||||||
end
|
end
|
||||||
|
|
||||||
bpos = string.find(source, name, bpos+1, true)
|
bpos = string.find(source, name, bpos + 1, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return source
|
return source
|
||||||
end
|
end
|
||||||
|
|
||||||
function compiler.compile_macro(line, macro_defs, list_defs)
|
function compiler.compile_macro(line, macro_defs, list_defs)
|
||||||
|
|
||||||
line = compiler.expand_lists_in(line, list_defs)
|
line = compiler.expand_lists_in(line, list_defs)
|
||||||
|
|
||||||
local ast, error_msg = parser.parse_filter(line)
|
local ast, error_msg = parser.parse_filter(line)
|
||||||
|
|
||||||
if (error_msg) then
|
if (error_msg) then
|
||||||
msg = "Compilation error when compiling \""..line.."\": ".. error_msg
|
msg = "Compilation error when compiling \"" .. line .. "\": " .. error_msg
|
||||||
return false, msg
|
return false, msg
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Simply as a validation step, try to expand all macros in this
|
-- Simply as a validation step, try to expand all macros in this
|
||||||
-- macro's condition. This changes the ast, so we make a copy
|
-- macro's condition. This changes the ast, so we make a copy
|
||||||
-- first.
|
-- first.
|
||||||
local ast_copy = copy_ast_obj(ast)
|
local ast_copy = copy_ast_obj(ast)
|
||||||
|
|
||||||
if (ast.type == "Rule") then
|
if (ast.type == "Rule") then
|
||||||
-- Line is a filter, so expand macro references
|
-- Line is a filter, so expand macro references
|
||||||
repeat
|
repeat
|
||||||
status, expanded = expand_macros(ast_copy, macro_defs, false)
|
status, expanded = expand_macros(ast_copy, macro_defs, false)
|
||||||
if status == false then
|
if status == false then
|
||||||
msg = "Compilation error when compiling \""..line.."\": ".. expanded
|
msg = "Compilation error when compiling \"" .. line .. "\": " .. expanded
|
||||||
return false, msg
|
return false, msg
|
||||||
end
|
end
|
||||||
until expanded == false
|
until expanded == false
|
||||||
|
|
||||||
else
|
else
|
||||||
return false, "Unexpected top-level AST type: "..ast.type
|
return false, "Unexpected top-level AST type: " .. ast.type
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, ast
|
return true, ast
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -229,32 +239,31 @@ end
|
|||||||
--]]
|
--]]
|
||||||
function compiler.compile_filter(name, source, macro_defs, list_defs)
|
function compiler.compile_filter(name, source, macro_defs, list_defs)
|
||||||
|
|
||||||
source = compiler.expand_lists_in(source, list_defs)
|
source = compiler.expand_lists_in(source, list_defs)
|
||||||
|
|
||||||
local ast, error_msg = parser.parse_filter(source)
|
local ast, error_msg = parser.parse_filter(source)
|
||||||
|
|
||||||
if (error_msg) then
|
if (error_msg) then
|
||||||
msg = "Compilation error when compiling \""..source.."\": "..error_msg
|
msg = "Compilation error when compiling \"" .. source .. "\": " .. error_msg
|
||||||
return false, msg
|
return false, msg
|
||||||
end
|
end
|
||||||
|
|
||||||
if (ast.type == "Rule") then
|
if (ast.type == "Rule") then
|
||||||
-- Line is a filter, so expand macro references
|
-- Line is a filter, so expand macro references
|
||||||
repeat
|
repeat
|
||||||
status, expanded = expand_macros(ast, macro_defs, false)
|
status, expanded = expand_macros(ast, macro_defs, false)
|
||||||
if status == false then
|
if status == false then
|
||||||
return false, expanded
|
return false, expanded
|
||||||
end
|
end
|
||||||
until expanded == false
|
until expanded == false
|
||||||
|
|
||||||
else
|
else
|
||||||
return false, "Unexpected top-level AST type: "..ast.type
|
return false, "Unexpected top-level AST type: " .. ast.type
|
||||||
end
|
end
|
||||||
|
|
||||||
filters = get_filters(ast)
|
filters = get_filters(ast)
|
||||||
|
|
||||||
return true, ast, filters
|
return true, ast, filters
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return compiler
|
return compiler
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
@@ -40,232 +39,258 @@ local space = lpeg.space
|
|||||||
|
|
||||||
-- creates an error message for the input string
|
-- creates an error message for the input string
|
||||||
local function syntaxerror(errorinfo, pos, msg)
|
local function syntaxerror(errorinfo, pos, msg)
|
||||||
local error_msg = "%s: syntax error, %s"
|
local error_msg = "%s: syntax error, %s"
|
||||||
return string.format(error_msg, pos, msg)
|
return string.format(error_msg, pos, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- gets the farthest failure position
|
-- gets the farthest failure position
|
||||||
local function getffp(s, i, t)
|
local function getffp(s, i, t)
|
||||||
return t.ffp or i, t
|
return t.ffp or i, t
|
||||||
end
|
end
|
||||||
|
|
||||||
-- gets the table that contains the error information
|
-- gets the table that contains the error information
|
||||||
local function geterrorinfo()
|
local function geterrorinfo()
|
||||||
return Cmt(Carg(1), getffp) * (C(V "OneWord") + Cc("EOF")) / function(t, u)
|
return Cmt(Carg(1), getffp) * (C(V "OneWord") + Cc("EOF")) / function(t, u)
|
||||||
t.unexpected = u
|
t.unexpected = u
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- creates an errror message using the farthest failure position
|
-- creates an errror message using the farthest failure position
|
||||||
local function errormsg()
|
local function errormsg()
|
||||||
return geterrorinfo() / function(t)
|
return geterrorinfo() / function(t)
|
||||||
local p = t.ffp or 1
|
local p = t.ffp or 1
|
||||||
local msg = "unexpected '%s', expecting %s"
|
local msg = "unexpected '%s', expecting %s"
|
||||||
msg = string.format(msg, t.unexpected, t.expected)
|
msg = string.format(msg, t.unexpected, t.expected)
|
||||||
return nil, syntaxerror(t, p, msg)
|
return nil, syntaxerror(t, p, msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reports a syntactic error
|
-- reports a syntactic error
|
||||||
local function report_error()
|
local function report_error()
|
||||||
return errormsg()
|
return errormsg()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- sets the farthest failure position and the expected tokens
|
--- sets the farthest failure position and the expected tokens
|
||||||
local function setffp(s, i, t, n)
|
local function setffp(s, i, t, n)
|
||||||
if not t.ffp or i > t.ffp then
|
if not t.ffp or i > t.ffp then
|
||||||
t.ffp = i
|
t.ffp = i
|
||||||
t.list = {}
|
t.list = {}
|
||||||
t.list[n] = n
|
t.list[n] = n
|
||||||
t.expected = "'" .. n .. "'"
|
t.expected = "'" .. n .. "'"
|
||||||
elseif i == t.ffp then
|
elseif i == t.ffp then
|
||||||
if not t.list[n] then
|
if not t.list[n] then
|
||||||
t.list[n] = n
|
t.list[n] = n
|
||||||
t.expected = "'" .. n .. "', " .. t.expected
|
t.expected = "'" .. n .. "', " .. t.expected
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function updateffp(name)
|
local function updateffp(name)
|
||||||
return Cmt(Carg(1) * Cc(name), setffp)
|
return Cmt(Carg(1) * Cc(name), setffp)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- regular combinators and auxiliary functions
|
-- regular combinators and auxiliary functions
|
||||||
|
|
||||||
local function token(pat, name)
|
local function token(pat, name)
|
||||||
return pat * V "Skip" + updateffp(name) * P(false)
|
return pat * V "Skip" + updateffp(name) * P(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function symb(str)
|
local function symb(str)
|
||||||
return token(P(str), str)
|
return token(P(str), str)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function kw(str)
|
local function kw(str)
|
||||||
return token(P(str) * -V "idRest", str)
|
return token(P(str) * -V "idRest", str)
|
||||||
end
|
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 {
|
||||||
end
|
type = "List",
|
||||||
|
elements = elements
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--http://lua-users.org/wiki/StringTrim
|
-- http://lua-users.org/wiki/StringTrim
|
||||||
function trim(s)
|
function trim(s)
|
||||||
if (type(s) ~= "string") then
|
if (type(s) ~= "string") then
|
||||||
return s
|
return s
|
||||||
end
|
end
|
||||||
return (s:gsub("^%s*(.-)%s*$", "%1"))
|
return (s:gsub("^%s*(.-)%s*$", "%1"))
|
||||||
end
|
end
|
||||||
parser.trim = trim
|
parser.trim = trim
|
||||||
|
|
||||||
local function terminal(tag)
|
local function terminal(tag)
|
||||||
-- Rather than trim the whitespace in this way, it would be nicer to exclude it from the capture...
|
-- Rather than trim the whitespace in this way, it would be nicer to exclude it from the capture...
|
||||||
return token(V(tag), tag) / function(tok)
|
return token(V(tag), tag) / function(tok)
|
||||||
val = tok
|
val = tok
|
||||||
if tag ~= "String" then
|
if tag ~= "String" then
|
||||||
val = trim(tok)
|
val = trim(tok)
|
||||||
end
|
end
|
||||||
return {type = tag, value = val}
|
return {
|
||||||
end
|
type = tag,
|
||||||
|
value = val
|
||||||
|
}
|
||||||
|
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 {
|
||||||
end
|
type = "BinaryBoolOp",
|
||||||
|
operator = op,
|
||||||
|
left = e1,
|
||||||
|
right = e2
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bool(pat, sep)
|
local function bool(pat, sep)
|
||||||
return Cf(pat * Cg(sep * pat) ^ 0, binaryop)
|
return Cf(pat * Cg(sep * pat) ^ 0, binaryop)
|
||||||
end
|
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 {
|
||||||
end
|
type = "BinaryRelOp",
|
||||||
|
operator = op,
|
||||||
|
left = e1,
|
||||||
|
right = e2
|
||||||
|
}
|
||||||
|
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 = {
|
||||||
V "Start", -- Entry rule
|
V "Start", -- Entry rule
|
||||||
Start = V "Skip" * (V "Comment" + V "Rule" / rule) ^ -1 * -1 + report_error(),
|
Start = V "Skip" * (V "Comment" + V "Rule" / rule) ^ -1 * -1 + report_error(),
|
||||||
-- Grammar
|
-- Grammar
|
||||||
Comment = P "#" * P(1) ^ 0,
|
Comment = P "#" * P(1) ^ 0,
|
||||||
Rule = V "Filter" / filter * ((V "Skip") ^ -1),
|
Rule = V "Filter" / filter * ((V "Skip") ^ -1),
|
||||||
Filter = V "OrExpression",
|
Filter = V "OrExpression",
|
||||||
OrExpression = bool(V "AndExpression", V "OrOp"),
|
OrExpression = bool(V "AndExpression", V "OrOp"),
|
||||||
AndExpression = bool(V "NotExpression", V "AndOp"),
|
AndExpression = bool(V "NotExpression", V "AndOp"),
|
||||||
NotExpression = V "UnaryBoolOp" * V "NotExpression" / unaryboolop + V "ExistsExpression",
|
NotExpression = V "UnaryBoolOp" * V "NotExpression" / unaryboolop + V "ExistsExpression",
|
||||||
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
|
Value = terminal "Number" + terminal "String" + terminal "BareString",
|
||||||
Value = terminal "Number" + terminal "String" + terminal "BareString",
|
InList = symb("(") * list(V "Value", symb(",")) * symb(")"),
|
||||||
InList = symb("(") * list(V "Value", symb(",")) * symb(")"),
|
-- Lexemes
|
||||||
-- Lexemes
|
Space = space ^ 1,
|
||||||
Space = space ^ 1,
|
Skip = (V "Space") ^ 0,
|
||||||
Skip = (V "Space") ^ 0,
|
idStart = alpha + P("_"),
|
||||||
idStart = alpha + P("_"),
|
idRest = alnum + P("_"),
|
||||||
idRest = alnum + P("_"),
|
Identifier = V "idStart" * V "idRest" ^ 0,
|
||||||
Identifier = V "idStart" * V "idRest" ^ 0,
|
Macro = V "idStart" * V "idRest" ^ 0 * -P ".",
|
||||||
Macro = V "idStart" * V "idRest" ^ 0 * -P ".",
|
Int = digit ^ 1,
|
||||||
Int = digit ^ 1,
|
PathString = (alnum + S ",.-_/*?") ^ 1,
|
||||||
PathString = (alnum + S ",.-_/*?") ^ 1,
|
PortRangeString = (V "Int" + S ":,") ^ 1,
|
||||||
PortRangeString = (V "Int" + S ":,") ^ 1,
|
Index = V "PortRangeString" + V "Int" + V "PathString",
|
||||||
Index = V "PortRangeString" + V "Int" + V "PathString",
|
FieldName = V "Identifier" * (P "." + V "Identifier") ^ 1 * (P "[" * V "Index" * P "]") ^ -1,
|
||||||
FieldName = V "Identifier" * (P "." + V "Identifier") ^ 1 * (P "[" * V "Index" * P "]") ^ -1,
|
Name = C(V "Identifier") * -V "idRest",
|
||||||
Name = C(V "Identifier") * -V "idRest",
|
Hex = (P("0x") + P("0X")) * xdigit ^ 1,
|
||||||
Hex = (P("0x") + P("0X")) * xdigit ^ 1,
|
Expo = S("eE") * S("+-") ^ -1 * digit ^ 1,
|
||||||
Expo = S("eE") * S("+-") ^ -1 * digit ^ 1,
|
Float = (((digit ^ 1 * P(".") * digit ^ 0) + (P(".") * digit ^ 1)) * V "Expo" ^ -1) + (digit ^ 1 * V "Expo"),
|
||||||
Float = (((digit ^ 1 * P(".") * digit ^ 0) + (P(".") * digit ^ 1)) * V "Expo" ^ -1) + (digit ^ 1 * V "Expo"),
|
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 '"' + P "'" *
|
||||||
String = (P '"' * C(((P "\\" * P(1)) + (P(1) - P '"')) ^ 0) * P '"' +
|
C(((P "\\" * P(1)) + (P(1) - P "'")) ^ 0) * P "'"),
|
||||||
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(">=") / ">=" + symb("<") /
|
||||||
RelOp = symb("=") / "=" + symb("==") / "==" + symb("!=") / "!=" + symb("<=") / "<=" + symb(">=") / ">=" +
|
"<" + symb(">") / ">" + symb("contains") / "contains" + symb("icontains") / "icontains" + symb("glob") / "glob" +
|
||||||
symb("<") / "<" +
|
symb("startswith") / "startswith" + symb("endswith") / "endswith",
|
||||||
symb(">") / ">" +
|
SetOp = kw("in") / "in" + kw("intersects") / "intersects" + kw("pmatch") / "pmatch",
|
||||||
symb("contains") / "contains" +
|
UnaryBoolOp = kw("not") / "not",
|
||||||
symb("icontains") / "icontains" +
|
ExistsOp = kw("exists") / "exists",
|
||||||
symb("glob") / "glob" +
|
-- for error reporting
|
||||||
symb("startswith") / "startswith" +
|
OneWord = V "Name" + V "Number" + V "String" + P(1)
|
||||||
symb("endswith") / "endswith",
|
|
||||||
SetOp = kw("in") / "in" + kw("intersects") / "intersects" + kw("pmatch") / "pmatch",
|
|
||||||
UnaryBoolOp = kw("not") / "not",
|
|
||||||
ExistsOp = kw("exists") / "exists",
|
|
||||||
-- for error reporting
|
|
||||||
OneWord = V "Name" + V "Number" + V "String" + P(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
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 = {
|
||||||
lpeg.setmaxstack(1000)
|
subject = subject
|
||||||
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
|
}
|
||||||
return ast, error_msg
|
lpeg.setmaxstack(1000)
|
||||||
|
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
|
||||||
|
return ast, error_msg
|
||||||
end
|
end
|
||||||
|
|
||||||
function print_ast(ast, level)
|
function print_ast(ast, level)
|
||||||
local t = ast.type
|
local t = ast.type
|
||||||
level = level or 0
|
level = level or 0
|
||||||
local prefix = string.rep(" ", level * 4)
|
local prefix = string.rep(" ", level * 4)
|
||||||
level = level + 1
|
level = level + 1
|
||||||
|
|
||||||
if t == "Rule" then
|
if t == "Rule" then
|
||||||
print_ast(ast.filter, level)
|
print_ast(ast.filter, level)
|
||||||
elseif t == "Filter" then
|
elseif t == "Filter" then
|
||||||
print_ast(ast.value, level)
|
print_ast(ast.value, level)
|
||||||
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
|
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
|
||||||
print(prefix .. ast.operator)
|
print(prefix .. ast.operator)
|
||||||
print_ast(ast.left, level)
|
print_ast(ast.left, level)
|
||||||
print_ast(ast.right, level)
|
print_ast(ast.right, level)
|
||||||
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
|
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
|
||||||
print(prefix .. ast.operator)
|
print(prefix .. ast.operator)
|
||||||
print_ast(ast.argument, level)
|
print_ast(ast.argument, level)
|
||||||
elseif t == "List" then
|
elseif t == "List" then
|
||||||
for i, v in ipairs(ast.elements) do
|
for i, v in ipairs(ast.elements) do
|
||||||
print_ast(v, level)
|
print_ast(v, level)
|
||||||
end
|
end
|
||||||
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
|
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
|
||||||
print(prefix .. t .. " " .. ast.value)
|
print(prefix .. t .. " " .. ast.value)
|
||||||
elseif t == "MacroDef" then
|
elseif t == "MacroDef" then
|
||||||
-- don't print for now
|
-- don't print for now
|
||||||
else
|
else
|
||||||
error("Unexpected type in print_ast: " .. t)
|
error("Unexpected type in print_ast: " .. t)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
parser.print_ast = print_ast
|
parser.print_ast = print_ast
|
||||||
|
|
||||||
@@ -275,32 +300,32 @@ parser.print_ast = print_ast
|
|||||||
-- cb(ast_node, ctx)
|
-- cb(ast_node, ctx)
|
||||||
-- ctx is optional.
|
-- ctx is optional.
|
||||||
function traverse_ast(ast, node_types, cb, ctx)
|
function traverse_ast(ast, node_types, cb, ctx)
|
||||||
local t = ast.type
|
local t = ast.type
|
||||||
|
|
||||||
if node_types[t] ~= nil then
|
if node_types[t] ~= nil then
|
||||||
cb(ast, ctx)
|
cb(ast, ctx)
|
||||||
end
|
end
|
||||||
|
|
||||||
if t == "Rule" then
|
if t == "Rule" then
|
||||||
traverse_ast(ast.filter, node_types, cb, ctx)
|
traverse_ast(ast.filter, node_types, cb, ctx)
|
||||||
elseif t == "Filter" then
|
elseif t == "Filter" then
|
||||||
traverse_ast(ast.value, node_types, cb, ctx)
|
traverse_ast(ast.value, node_types, cb, ctx)
|
||||||
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
|
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
|
||||||
traverse_ast(ast.left, node_types, cb, ctx)
|
traverse_ast(ast.left, node_types, cb, ctx)
|
||||||
traverse_ast(ast.right, node_types, cb, ctx)
|
traverse_ast(ast.right, node_types, cb, ctx)
|
||||||
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
|
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
|
||||||
traverse_ast(ast.argument, node_types, cb, ctx)
|
traverse_ast(ast.argument, node_types, cb, ctx)
|
||||||
elseif t == "List" then
|
elseif t == "List" then
|
||||||
for i, v in ipairs(ast.elements) do
|
for i, v in ipairs(ast.elements) do
|
||||||
traverse_ast(v, node_types, cb, ctx)
|
traverse_ast(v, node_types, cb, ctx)
|
||||||
end
|
end
|
||||||
elseif t == "MacroDef" then
|
elseif t == "MacroDef" then
|
||||||
traverse_ast(ast.value, node_types, cb, ctx)
|
traverse_ast(ast.value, node_types, cb, ctx)
|
||||||
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
|
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
|
||||||
-- do nothing, no traversal needed
|
-- do nothing, no traversal needed
|
||||||
else
|
else
|
||||||
error("Unexpected type in traverse_ast: " .. t)
|
error("Unexpected type in traverse_ast: " .. t)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
parser.traverse_ast = traverse_ast
|
parser.traverse_ast = traverse_ast
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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,55 +12,52 @@
|
|||||||
-- 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 = {}
|
||||||
|
|
||||||
function sinsp_rule_utils.check_for_ignored_syscalls_events(ast, filter_type, source)
|
function sinsp_rule_utils.check_for_ignored_syscalls_events(ast, filter_type, source)
|
||||||
|
|
||||||
function check_syscall(val)
|
function check_syscall(val)
|
||||||
if ignored_syscalls[val] then
|
if ignored_syscalls[val] then
|
||||||
error("Ignored syscall \""..val.."\" in "..filter_type..": "..source)
|
error("Ignored syscall \"" .. val .. "\" in " .. filter_type .. ": " .. source)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function check_event(val)
|
function check_event(val)
|
||||||
if ignored_events[val] then
|
if ignored_events[val] then
|
||||||
error("Ignored event \""..val.."\" in "..filter_type..": "..source)
|
error("Ignored event \"" .. val .. "\" in " .. filter_type .. ": " .. source)
|
||||||
end
|
end
|
||||||
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
|
for i, v in ipairs(node.right.elements) do
|
||||||
node.operator == "pmatch") then
|
if v.type == "BareString" then
|
||||||
for i, v in ipairs(node.right.elements) do
|
if node.left.value == "evt.type" then
|
||||||
if v.type == "BareString" then
|
check_event(v.value)
|
||||||
if node.left.value == "evt.type" then
|
else
|
||||||
check_event(v.value)
|
check_syscall(v.value)
|
||||||
else
|
end
|
||||||
check_syscall(v.value)
|
end
|
||||||
end
|
end
|
||||||
end
|
else
|
||||||
end
|
if node.right.type == "BareString" then
|
||||||
else
|
if node.left.value == "evt.type" then
|
||||||
if node.right.type == "BareString" then
|
check_event(node.right.value)
|
||||||
if node.left.value == "evt.type" then
|
else
|
||||||
check_event(node.right.value)
|
check_syscall(node.right.value)
|
||||||
else
|
end
|
||||||
check_syscall(node.right.value)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
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
|
||||||
@@ -75,125 +72,129 @@ end
|
|||||||
|
|
||||||
function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes, verbose)
|
function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes, verbose)
|
||||||
|
|
||||||
local evttypes = {}
|
local evttypes = {}
|
||||||
local syscallnums = {}
|
local syscallnums = {}
|
||||||
local evtnames = {}
|
local evtnames = {}
|
||||||
local found_event = false
|
local found_event = false
|
||||||
local found_not = false
|
local found_not = false
|
||||||
local found_event_after_not = false
|
local found_event_after_not = false
|
||||||
|
|
||||||
function cb(node)
|
function cb(node)
|
||||||
if node.type == "UnaryBoolOp" then
|
if node.type == "UnaryBoolOp" then
|
||||||
if node.operator == "not" then
|
if node.operator == "not" then
|
||||||
found_not = true
|
found_not = true
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if node.operator == "!=" then
|
if node.operator == "!=" then
|
||||||
found_not = true
|
found_not = true
|
||||||
end
|
end
|
||||||
if node.left.type == "FieldName" and node.left.value == "evt.type" then
|
if node.left.type == "FieldName" and node.left.value == "evt.type" then
|
||||||
found_event = true
|
found_event = true
|
||||||
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
|
for i, v in ipairs(node.right.elements) do
|
||||||
node.operator == "pmatch") then
|
if v.type == "BareString" then
|
||||||
for i, v in ipairs(node.right.elements) do
|
|
||||||
if v.type == "BareString" then
|
|
||||||
|
|
||||||
-- The event must be a known event
|
-- The event must be a known event
|
||||||
if events[v.value] == nil and syscalls[v.value] == nil then
|
if events[v.value] == nil and syscalls[v.value] == nil then
|
||||||
error("Unknown event/syscall \""..v.value.."\" in filter: "..source)
|
error("Unknown event/syscall \"" .. v.value .. "\" in filter: " .. source)
|
||||||
end
|
end
|
||||||
|
|
||||||
evtnames[v.value] = 1
|
evtnames[v.value] = 1
|
||||||
if events[v.value] ~= nil then
|
if events[v.value] ~= nil then
|
||||||
for id in string.gmatch(events[v.value], "%S+") do
|
for id in string.gmatch(events[v.value], "%S+") do
|
||||||
evttypes[id] = 1
|
evttypes[id] = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if syscalls[v.value] ~= nil then
|
if syscalls[v.value] ~= nil then
|
||||||
for id in string.gmatch(syscalls[v.value], "%S+") do
|
for id in string.gmatch(syscalls[v.value], "%S+") do
|
||||||
syscallnums[id] = 1
|
syscallnums[id] = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if node.right.type == "BareString" then
|
if node.right.type == "BareString" then
|
||||||
|
|
||||||
-- The event must be a known event
|
-- The event must be a known event
|
||||||
if events[node.right.value] == nil and syscalls[node.right.value] == nil then
|
if events[node.right.value] == nil and syscalls[node.right.value] == nil then
|
||||||
error("Unknown event/syscall \""..node.right.value.."\" in filter: "..source)
|
error("Unknown event/syscall \"" .. node.right.value .. "\" in filter: " .. source)
|
||||||
end
|
end
|
||||||
|
|
||||||
evtnames[node.right.value] = 1
|
evtnames[node.right.value] = 1
|
||||||
if events[node.right.value] ~= nil then
|
if events[node.right.value] ~= nil then
|
||||||
for id in string.gmatch(events[node.right.value], "%S+") do
|
for id in string.gmatch(events[node.right.value], "%S+") do
|
||||||
evttypes[id] = 1
|
evttypes[id] = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if syscalls[node.right.value] ~= nil then
|
if syscalls[node.right.value] ~= nil then
|
||||||
for id in string.gmatch(syscalls[node.right.value], "%S+") do
|
for id in string.gmatch(syscalls[node.right.value], "%S+") do
|
||||||
syscallnums[id] = 1
|
syscallnums[id] = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
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")
|
||||||
end
|
io.stderr:write(
|
||||||
evttypes = {}
|
" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n")
|
||||||
syscallnums = {}
|
end
|
||||||
evtnames = {}
|
evttypes = {}
|
||||||
end
|
syscallnums = {}
|
||||||
|
evtnames = {}
|
||||||
|
end
|
||||||
|
|
||||||
if found_event_after_not then
|
if found_event_after_not then
|
||||||
if warn_evttypes == true then
|
if warn_evttypes == true then
|
||||||
io.stderr:write("Rule "..name..": warning (trailing-evttype):\n")
|
io.stderr:write("Rule " .. name .. ": warning (trailing-evttype):\n")
|
||||||
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(
|
||||||
io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
|
" This has a performance penalty, as the rule can not be limited to specific event types.\n")
|
||||||
io.stderr:write(" replacing negative matches with positive matches if possible.\n")
|
io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
|
||||||
end
|
io.stderr:write(" replacing negative matches with positive matches if possible.\n")
|
||||||
evttypes = {}
|
end
|
||||||
syscallnums = {}
|
evttypes = {}
|
||||||
evtnames = {}
|
syscallnums = {}
|
||||||
end
|
evtnames = {}
|
||||||
|
end
|
||||||
|
|
||||||
evtnames_only = {}
|
evtnames_only = {}
|
||||||
local num_evtnames = 0
|
local num_evtnames = 0
|
||||||
for name, dummy in pairs(evtnames) do
|
for name, dummy in pairs(evtnames) do
|
||||||
table.insert(evtnames_only, name)
|
table.insert(evtnames_only, name)
|
||||||
num_evtnames = num_evtnames + 1
|
num_evtnames = num_evtnames + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if num_evtnames == 0 then
|
if num_evtnames == 0 then
|
||||||
table.insert(evtnames_only, "all")
|
table.insert(evtnames_only, "all")
|
||||||
end
|
end
|
||||||
|
|
||||||
table.sort(evtnames_only)
|
table.sort(evtnames_only)
|
||||||
|
|
||||||
if verbose then
|
if verbose then
|
||||||
io.stderr:write("Event types/Syscalls for rule "..name..": "..table.concat(evtnames_only, ",").."\n")
|
io.stderr:write("Event types/Syscalls for rule " .. name .. ": " .. table.concat(evtnames_only, ",") .. "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
return evttypes, syscallnums
|
return evttypes, syscallnums
|
||||||
end
|
end
|
||||||
|
|
||||||
return sinsp_rule_utils
|
return sinsp_rule_utils
|
||||||
|
|||||||
Reference in New Issue
Block a user