Compare commits

..

3 Commits

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Mark Stemm <mark.stemm@gmail.com>
2022-01-12 09:59:42 -08:00
123 changed files with 4251 additions and 4726 deletions

View File

@@ -13,7 +13,7 @@ jobs:
command: apk update
- run:
name: Install build dependencies
command: apk add g++ gcc cmake make git bash perl linux-headers autoconf automake m4 libtool elfutils-dev libelf-static patch binutils
command: apk add g++ gcc cmake cmake make git bash perl linux-headers autoconf automake m4 libtool elfutils-dev libelf-static patch binutils
- run:
name: Prepare project
command: |
@@ -176,6 +176,38 @@ jobs:
pushd build
make tests
popd
# Build using CentOS 8
# This build is static, dependencies are bundled in the Falco binary
"build/centos8":
docker:
- image: centos:8
steps:
- checkout
- run:
name: Update base image
command: dnf update -y
- run:
name: Install dependencies
command: dnf install gcc gcc-c++ git make cmake autoconf automake pkg-config patch libtool elfutils-libelf-devel diffutils kernel-devel kernel-headers kernel-core clang llvm which -y
- run:
name: Prepare project
command: |
mkdir build
pushd build
cmake -DBUILD_BPF=On -DUSE_BUNDLED_DEPS=On ..
popd
- run:
name: Build
command: |
pushd build
KERNELDIR=/lib/modules/$(ls /lib/modules)/build make -j4 all
popd
- run:
name: Run unit tests
command: |
pushd build
make tests
popd
# Build using our own builder base image using centos 7
# This build is static, dependencies are bundled in the Falco binary
"build/centos7":
@@ -359,15 +391,9 @@ jobs:
/source/falco/scripts/publish-rpm -f /build/release/falco-${FALCO_VERSION}-x86_64.rpm -r rpm-dev
- run:
name: Publish bin-dev
command: |
FALCO_VERSION=$(cat /build/release/userspace/falco/config_falco.h | grep 'FALCO_VERSION ' | cut -d' ' -f3 | sed -e 's/^"//' -e 's/"$//')
/source/falco/scripts/publish-bin -f /build/release/falco-${FALCO_VERSION}-x86_64.tar.gz -r bin-dev -a x86_64
- run:
name: Publish bin-static-dev
command: |
FALCO_VERSION=$(cat /build-static/release/userspace/falco/config_falco.h | grep 'FALCO_VERSION ' | cut -d' ' -f3 | sed -e 's/^"//' -e 's/"$//')
cp -f /build-static/release/falco-${FALCO_VERSION}-x86_64.tar.gz /build-static/release/falco-${FALCO_VERSION}-static-x86_64.tar.gz
/source/falco/scripts/publish-bin -f /build-static/release/falco-${FALCO_VERSION}-static-x86_64.tar.gz -r bin-dev -a x86_64
/source/falco/scripts/publish-bin -f /build-static/release/falco-${FALCO_VERSION}-x86_64.tar.gz -r bin-dev -a x86_64
"publish/packages-deb-dev":
docker:
- image: docker.io/debian:stable
@@ -479,15 +505,9 @@ jobs:
/source/falco/scripts/publish-rpm -f /build/release/falco-${FALCO_VERSION}-x86_64.rpm -r rpm
- run:
name: Publish bin
command: |
FALCO_VERSION=$(cat /build/release/userspace/falco/config_falco.h | grep 'FALCO_VERSION ' | cut -d' ' -f3 | sed -e 's/^"//' -e 's/"$//')
/source/falco/scripts/publish-bin -f /build/release/falco-${FALCO_VERSION}-x86_64.tar.gz -r bin -a x86_64
- run:
name: Publish bin-static
command: |
FALCO_VERSION=$(cat /build-static/release/userspace/falco/config_falco.h | grep 'FALCO_VERSION ' | cut -d' ' -f3 | sed -e 's/^"//' -e 's/"$//')
cp -f /build-static/release/falco-${FALCO_VERSION}-x86_64.tar.gz /build-static/release/falco-${FALCO_VERSION}-static-x86_64.tar.gz
/source/falco/scripts/publish-bin -f /build-static/release/falco-${FALCO_VERSION}-static-x86_64.tar.gz -r bin -a x86_64
/source/falco/scripts/publish-bin -f /build-static/release/falco-${FALCO_VERSION}-x86_64.tar.gz -r bin -a x86_64
"publish/packages-deb":
docker:
- image: docker.io/debian:stable
@@ -598,6 +618,7 @@ workflows:
- "build/ubuntu-focal"
- "build/ubuntu-focal-debug"
- "build/ubuntu-bionic"
- "build/centos8"
- "build/centos7"
- "build/centos7-debug"
- "tests/integration":
@@ -661,7 +682,7 @@ workflows:
only: master
requires:
- publish/docker-dev
# - "quality/static-analysis" # This is temporarily disabled: https://github.com/falcosecurity/falco/issues/1526
# - "quality/static-analysis" # This is temporarly disabled: https://github.com/falcosecurity/falco/issues/1526
release:
jobs:
- "build/musl":

View File

@@ -7,7 +7,7 @@ line_width = 120
# How many spaces to tab for indent
tab_size = 2
# If arg lists are longer than this, break them always
# If arglists are longer than this, break them always
max_subargs_per_line = 3
# If true, separate flow control names from their parentheses with a space
@@ -21,7 +21,7 @@ separate_fn_name_with_space = False
dangle_parens = False
# If the statement spelling length (including space and parenthesis is larger
# than the tab width by more than this among, then force reject un-nested
# than the tab width by more than this amoung, then force reject un-nested
# layouts.
max_prefix_chars = 2
@@ -54,7 +54,7 @@ always_wrap = []
algorithm_order = [0, 1, 2, 3, 4]
# If true, the argument lists which are known to be sortable will be sorted
# lexicographically
# lexicographicall
enable_sort = True
# If true, the parsers may infer whether or not an argument list is sortable

View File

@@ -1,3 +0,0 @@
aks
creat
chage

View File

@@ -1,14 +0,0 @@
name: Codespell
on:
pull_request:
jobs:
codespell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: codespell-project/actions-codespell@master
with:
skip: .git
ignore_words_file: .codespellignore
check_filenames: true
check_hidden: true

7
.gitignore vendored
View File

@@ -10,6 +10,11 @@ test/.phoronix-test-suite
test/results*.json.*
test/build
userspace/engine/lua/lyaml
userspace/engine/lua/lyaml.lua
.vscode/*
*.idea*
.luacheckcache
*.idea*

8
.luacheckrc Normal file
View File

@@ -0,0 +1,8 @@
std = "min"
cache = true
include_files = {
"userspace/engine/lua/*.lua",
"userspace/engine/lua/lyaml/*.lua",
"*.luacheckrc"
}
exclude_files = {"build"}

View File

@@ -24,16 +24,12 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Coveo](https://www.coveo.com/) - Coveo stitches together content and data, learning from every interaction, to tailor every experience using AI to drive growth, satisfy customers and develop employee proficiency. All Falco events are centralized in our SIEM for analysis. Understanding what is running on production servers, and the context around why things are running is even more tricky now that we have further abstractions with containers and orchestration systems. Falco is giving us a good visibility inside containers and complement other Host and Network Intrusion Detection Systems. In a near future, we expect to deploy serverless functions to take action when Falco identifies patterns worth taking action for.
* [Fairwinds](https://fairwinds.com/) - [Fairwinds Insights](https://fairwinds.com/insights), Kubernetes governance software, integrates Falco to offer a single pane of glass view into potential security incidents. Insights adds out-of-the-box integrations and rules filter to reduce alert fatigue and improve security response. The platform adds security prevention, detection, and response capabilities to your existing Kubernetes infrastructure. Security and DevOps teams benefit from a centralized view of container security vulnerability scanning and runtime container security.
* [Frame.io](https://frame.io/) - Frame.io is a cloud-based (SaaS) video review and collaboration platform that enables users to securely upload source media, work-in-progress edits, dailies, and more into private workspaces where they can invite their team and clients to collaborate on projects. Understanding what is running on production servers, and the context around why things are running is even more tricky now that we have further abstractions like Docker and Kubernetes. To get this needed visibility into our system, we rely on Falco. Falco's ability to collect raw system calls such as open, connect, exec, along with their arguments offer key insights on what is happening on the production system and became the foundation of our intrusion detection and alerting system.
* [Giant Swarm](https://www.giantswarm.io/) - Giant Swarm manages Kubernetes clusters and infrastructure for enterprises across multiple cloud providers as well as several flavors of on-premises data centers. Our platform provisions and monitors pure "vanilla" Kubernetes clusters which can be augmented with managed solutions to many common Kubernetes challenges, including security. We use Falco for anomaly detection as part of our collection of entirely open-source tools for securing our own clusters, and offer the same capabilities to our customers as part of our [managed security offering](https://docs.giantswarm.io/app-platform/apps/security/).
* [GitLab](https://about.gitlab.com/direction/defend/container_host_security/) - GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate. GitLab Ultimate provides the single tool teams need to find, triage, and fix vulnerabilities in applications, services, and cloud-native environments enabling them to manage their risk. This provides them with repeatable, defensible processes that automate security and compliance policies. GitLab includes a tight integration with Falco, allowing users to defend their containerized applications from attacks while running in production.
* [gVisor](https://gvisor.dev/) - gVisor secures Kubernetes, containers, and workloads via an alternate execution environment that handles system calls in user space, blocking security issues before they reach the underlying host. gVisor provides defense-in-depth, protection against untrusted code execution, and a secure-by-default Kubernetes experience where containers are a security boundary. Falco can be used with gVisor to detect unusual or suspicious activity using its threat detection engine on top of gVisor runtime execution information.
* [League](https://league.com/ca/) - League provides health benefits management services to help employees understand and get the most from their benefits, and employers to provide effective, efficient plans. Falco is used to monitor our deployed services on Kubernetes, protecting against malicious access to containers which could lead to leaks of PHI or other sensitive data. The Falco alerts are logged in Stackdriver for grouping and further analysis. In the future, we're hoping for integrations with Prometheus and AlertManager as well.
* [Logz.io](https://logz.io/) - Logz.io is a cloud observability platform for modern engineering teams. The Logz.io platform consists of three products — Log Management, Infrastructure Monitoring, and Cloud SIEM — that work together to unify the jobs of monitoring, troubleshooting, and security. We empower engineers to deliver better software by offering the world's most popular open source observability tools — the ELK Stack, Grafana, and Jaeger — in a single, easy to use, and powerful platform purpose-built for monitoring distributed cloud environments. Cloud SIEM supports data from multiple sources, including Falco's alerts, and offers useful rules and dashboards content to visualize and manage incidents across your systems in a unified UI.
@@ -43,10 +39,10 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Pocteo](https://pocteo.co) - Pocteo helps with Kubernetes adoption in enterprises by providing a variety of services such as training, consulting, auditing and mentoring. We build CI/CD pipelines the GitOps way, as well as design and run k8s clusters. Pocteo uses Falco as a runtime monitoring system to secure clients' workloads against suspicious behavior and ensure k8s pods immutability. We also use Falco to collect, process and act on security events through a response engine and serverless functions.
* [Preferral](https://www.preferral.com) - Preferral is a HIPAA-compliant platform for Referral Management and Online Referral Forms. Preferral streamlines the referral process for patients, specialists and their referral partners. By automating the referral process, referring practices spend less time on the phone, manual efforts are eliminated, and patients get the right care from the right specialist. Preferral leverages Falco to provide a Host Intrusion Detection System to meet their HIPAA compliance requirements.
* [Preferral](https://www.preferral.com) - Preferral is a HIPAA-compliant platform for Referral Management and Online Referral Forms. Preferral streamlines the referral process for patients, specialists and their referral partners. By automating the referral process, referring practices spend less time on the phone, manual efforts are eliminated, and patients get the right care from the right specialist. Preferral leverages Falco to provide a Host Intrusion Detection System to meet their HIPPA compliance requirements.
* https://hipaa.preferral.com/01-preferral_hipaa_compliance/
* [Qonto](https://qonto.com) - Qonto is a modern banking for SMEs and freelancers. Qonto provides a fully featured business account with a simplified accounting flow. Falco is used by our SecOps team to detect suspicious behaviors in our clusters.
* [Qonto](https://qonto.com) - Qonto is a modern banking for SMEs and freelancers. Qonto provides a fully featured business account with a simplified accounting flow. Falco is used by our SecOps team to detect suspicous behaviors in our clusters.
* [Raft](https://goraft.tech) - Raft is a government contractor that offers cloud-native solutions across many different agencies including DoD (Department of Defense), HHS (Health and Human Services), as well as within CFPB (Consumer Finance Protection Bureau). Raft leverages Falco to detect threats in our client's Kubernetes clusters and as a Host Intrusion Detection System. Raft proudly recommends Falco across all our different projects.
@@ -68,7 +64,7 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Yahoo! JAPAN](https://www.yahoo.co.jp/) Yahoo! JAPAN is a leading company of internet in Japan. We build an AI Platform in our private cloud and provide it to scientists in our company. AI Platform is a multi-tenant Kubernetes environment and more flexible, faster, more efficient Machine Learning environment. Falco is used to detect unauthorized commands and malicious access and our AI Platform is monitored and alerted by Falco.
* [Sysdig](https://www.sysdig.com/) Sysdig originally created Falco in 2016 to detect unexpected or suspicious activity using a rules engine on top of the data that comes from the sysdig kernel system call driver. Sysdig provides tooling to help with vulnerability management, compliance, detection, incident response and forensics in Cloud-native environments. Sysdig Secure has extended Falco to include: a rule library, the ability to update macros, lists & rules via the user interface and API, automated tuning of rules, and rule creation based on profiling known system behavior. On top of the basic Falco rules, Sysdig Secure implements the concept of a "Security policy" that can comprise several rules which are evaluated for a user-defined infrastructure scope like Kubernetes namespaces, OpenShift clusters, deployment workload, cloud regions etc.
* [Sysdig](https://www.sysdig.com/) Sysdig originally created Falco in 2016 to detect unexpected or suspicious activity using a rules engine on top of the data that comes from the sysdig kernel system call probe. Sysdig provides tooling to help with vulnerability management, compliance, detection, incident response and forensics in Cloud-native environments. Sysdig Secure has extended Falco to include: a rule library, the ability to update macros, lists & rules via the user interface and API, automated tuning of rules, and rule creation based on profiling known system behavior. On top of the basic Falco rules, Sysdig Secure implements the concept of a "Security policy" that can comprise several rules which are evaluated for a user-defined infrastructure scope like Kubernetes namespaces, OpenShift clusters, deployment workload, cloud regions etc.
## Projects that use Falco libs

View File

@@ -1,153 +1,5 @@
# Change Log
## v0.31.1
Released on 2022-03-09
### Major Changes
* new: add a new drop category `n_drops_scratch_map` [[#1916](https://github.com/falcosecurity/falco/pull/1916)] - [@Andreagit97](https://github.com/Andreagit97)
* new: allow to specify multiple --cri options [[#1893](https://github.com/falcosecurity/falco/pull/1893)] - [@FedeDP](https://github.com/FedeDP)
### Minor Changes
* refactor(userspace/falco): replace direct getopt_long() cmdline option parsing with third-party cxxopts library. [[#1886](https://github.com/falcosecurity/falco/pull/1886)] - [@mstemm](https://github.com/mstemm)
* update: driver version is b7eb0dd [[#1923](https://github.com/falcosecurity/falco/pull/1923)] - [@LucaGuerra](https://github.com/LucaGuerra)
### Bug Fixes
* fix(userspace/falco): correct plugins init config conversion from YAML to JSON [[#1907](https://github.com/falcosecurity/falco/pull/1907)] - [@jasondellaluce](https://github.com/jasondellaluce)
* fix(userspace/engine): for rules at the informational level being loaded at the notice level [[#1885](https://github.com/falcosecurity/falco/pull/1885)] - [@mike-stewart](https://github.com/mike-stewart)
* chore(userspace/falco): fixes truncated -b option description. [[#1915](https://github.com/falcosecurity/falco/pull/1915)] - [@andreabonanno](https://github.com/andreabonanno)
* update(falco): updates usage description for -o, --option [[#1903](https://github.com/falcosecurity/falco/pull/1903)] - [@andreabonanno](https://github.com/andreabonanno)
### Security Fixes
* Fix for a TOCTOU issue that could lead to rule bypass (CVE-2022-26316). For more information, see the [advisory](https://github.com/falcosecurity/falco/security/advisories/GHSA-6v9j-2vm2-ghf7).
### Rule Changes
* rule(Detect outbound connections to common miner pool ports): fix url in rule output [[#1918](https://github.com/falcosecurity/falco/pull/1918)] - [@jsoref](https://github.com/jsoref)
* rule(macro somebody_becoming_themself): renaming macro to somebody_becoming_themselves [[#1918](https://github.com/falcosecurity/falco/pull/1918)] - [@jsoref](https://github.com/jsoref)
* rule(list package_mgmt_binaries): `npm` added [[#1866](https://github.com/falcosecurity/falco/pull/1866)] - [@rileydakota](https://github.com/rileydakota)
* rule(Launch Package Management Process in Container): support for detecting `npm` usage [[#1866](https://github.com/falcosecurity/falco/pull/1866)] - [@rileydakota](https://github.com/rileydakota)
* rule(Polkit Local Privilege Escalation Vulnerability): new rule created to detect CVE-2021-4034 [[#1877](https://github.com/falcosecurity/falco/pull/1877)] - [@darryk10](https://github.com/darryk10)
* rule(macro: modify_shell_history): avoid false-positive alerts triggered by modifications to .zsh_history.new and .zsh_history.LOCK files [[#1832](https://github.com/falcosecurity/falco/pull/1832)] - [@m4wh6k](https://github.com/m4wh6k)
* rule(macro: truncate_shell_history): avoid false-positive alerts triggered by modifications to .zsh_history.new and .zsh_history.LOCK files [[#1832](https://github.com/falcosecurity/falco/pull/1832)] - [@m4wh6k](https://github.com/m4wh6k)
* rule(macro sssd_writing_krb): fixed a false-positive alert that was being generated when SSSD updates /etc/krb5.keytab [[#1825](https://github.com/falcosecurity/falco/pull/1825)] - [@mac-chaffee](https://github.com/mac-chaffee)
* rule(macro write_etc_common): fixed a false-positive alert that was being generated when SSSD updates /etc/krb5.keytab [[#1825](https://github.com/falcosecurity/falco/pull/1825)] - [@mac-chaffee](https://github.com/mac-chaffee)
* upgrade macro(keepalived_writing_conf) [[#1742](https://github.com/falcosecurity/falco/pull/1742)] - [@pabloopez](https://github.com/pabloopez)
* rule_output(Delete Bucket Public Access Block) typo [[#1888](https://github.com/falcosecurity/falco/pull/1888)] - [@pabloopez](https://github.com/pabloopez)
### Non user-facing changes
* fix(build): fix civetweb linking in cmake module [[#1919](https://github.com/falcosecurity/falco/pull/1919)] - [@LucaGuerra](https://github.com/LucaGuerra)
* chore(userspace/engine): remove unused lua functions and state vars [[#1908](https://github.com/falcosecurity/falco/pull/1908)] - [@jasondellaluce](https://github.com/jasondellaluce)
* fix(userspace/falco): applies FALCO_INSTALL_CONF_FILE as the default … [[#1900](https://github.com/falcosecurity/falco/pull/1900)] - [@andreabonanno](https://github.com/andreabonanno)
* fix(scripts): correct typo in `falco-driver-loader` help message [[#1899](https://github.com/falcosecurity/falco/pull/1899)] - [@leogr](https://github.com/leogr)
* update(build)!: replaced various `PROBE` with `DRIVER` where necessary. [[#1887](https://github.com/falcosecurity/falco/pull/1887)] - [@FedeDP](https://github.com/FedeDP)
* Add [Fairwinds](https://fairwinds.com) to the adopters list [[#1917](https://github.com/falcosecurity/falco/pull/1917)] - [@sudermanjr](https://github.com/sudermanjr)
* build(cmake): several cmake changes to speed up/simplify builds for external projects and copying files from source-to-build directories [[#1905](https://github.com/falcosecurity/falco/pull/1905)] - [@mstemm](https://github.com/mstemm)
## v0.31.0
Released on 2022-01-31
### Major Changes
* new: add support for plugins to extend Falco functionality to new event sources and custom fields [[#1753](https://github.com/falcosecurity/falco/pull/1753)] - [@mstemm](https://github.com/mstemm)
* new: add ability to set User-Agent http header when sending http output. Provide default value of 'falcosecurity/falco'. [[#1850](https://github.com/falcosecurity/falco/pull/1850)] - [@yoshi314](https://github.com/yoshi314)
* new(configuration): support defining plugin init config as a YAML [[#1852](https://github.com/falcosecurity/falco/pull/1852)] - [@jasondellaluce](https://github.com/jasondellaluce)
### Minor Changes
* rules: add the official Falco ECR repository to rules [[#1817](https://github.com/falcosecurity/falco/pull/1817)] - [@calvinbui](https://github.com/calvinbui)
* build: update CircleCI machine image for eBPF tests to a newer version of ubuntu [[#1764](https://github.com/falcosecurity/falco/pull/1764)] - [@mstemm](https://github.com/mstemm)
* update(engine): refactor Falco engine to be agnostic to specific event sources [[#1715](https://github.com/falcosecurity/falco/pull/1715)] - [@mstemm](https://github.com/mstemm)
* build: upgrade civetweb to v1.15 [[#1782](https://github.com/falcosecurity/falco/pull/1782)] - [@FedeDP](https://github.com/FedeDP)
* update: driver version is 319368f1ad778691164d33d59945e00c5752cd27 now [[#1861](https://github.com/falcosecurity/falco/pull/1861)] - [@FedeDP](https://github.com/FedeDP)
* build: allow using local libs source dir by setting `FALCOSECURITY_LIBS_SOURCE_DIR` in cmake [[#1791](https://github.com/falcosecurity/falco/pull/1791)] - [@jasondellaluce](https://github.com/jasondellaluce)
* build: the statically linked binary package is now published with the `-static` suffix [[#1873](https://github.com/falcosecurity/falco/pull/1873)] - [@LucaGuerra](https://github.com/LucaGuerra)
* update!: removed "--alternate-lua-dir" cmdline option as lua scripts are now embedded in Falco executable. [[#1872](https://github.com/falcosecurity/falco/pull/1872)] - [@FedeDP](https://github.com/FedeDP)
* build: switch to dynamic build for the binary package (`.tar.gz`) [[#1853](https://github.com/falcosecurity/falco/pull/1853)] - [@LucaGuerra](https://github.com/LucaGuerra)
* update: simpleconsumer filtering is now being done at kernel level [[#1846](https://github.com/falcosecurity/falco/pull/1846)] - [@FedeDP](https://github.com/FedeDP)
* update(scripts/falco-driver-loader): first try to load the latest kmod version, then fallback to an already installed if any [[#1863](https://github.com/falcosecurity/falco/pull/1863)] - [@leogr](https://github.com/leogr)
* refactor: clean up --list output with better formatting and no duplicate sections across event sources. [[#1816](https://github.com/falcosecurity/falco/pull/1816)] - [@mstemm](https://github.com/mstemm)
* update: embed .lua files used to load/compile rules into the main falco executable, for simplicity and to avoid tampering. [[#1843](https://github.com/falcosecurity/falco/pull/1843)] - [@mstemm](https://github.com/mstemm)
* update: support non-enumerable event sources in gRPC outputs service [[#1840](https://github.com/falcosecurity/falco/pull/1840)] - [@jasondellaluce](https://github.com/jasondellaluce)
* docs: add jasondellaluce to OWNERS [[#1818](https://github.com/falcosecurity/falco/pull/1818)] - [@jasondellaluce](https://github.com/jasondellaluce)
* chore: --list option can be used to selectively list fields related to new sources that are introduced by plugins [[#1839](https://github.com/falcosecurity/falco/pull/1839)] - [@loresuso](https://github.com/loresuso)
* update(userspace/falco): support arbitrary-depth nested values in YAML configuration [[#1792](https://github.com/falcosecurity/falco/pull/1792)] - [@jasondellaluce](https://github.com/jasondellaluce)
* build: bump FakeIt version to 2.0.9 [[#1797](https://github.com/falcosecurity/falco/pull/1797)] - [@jasondellaluce](https://github.com/jasondellaluce)
* update: allow append of new exceptions to rules [[#1780](https://github.com/falcosecurity/falco/pull/1780)] - [@sai-arigeli](https://github.com/sai-arigeli)
* update: Linux packages are now signed with SHA256 [[#1758](https://github.com/falcosecurity/falco/pull/1758)] - [@twa16](https://github.com/twa16)
### Bug Fixes
* fix(scripts/falco-driver-loader): fix for SELinux insmod denials [[#1756](https://github.com/falcosecurity/falco/pull/1756)] - [@dwindsor](https://github.com/dwindsor)
* fix(scripts/falco-driver-loader): correctly clean loaded drivers when using `--clean` [[#1795](https://github.com/falcosecurity/falco/pull/1795)] - [@jasondellaluce](https://github.com/jasondellaluce)
* fix(userspace/falco): in case output_file cannot be opened, throw a falco exception [[#1773](https://github.com/falcosecurity/falco/pull/1773)] - [@FedeDP](https://github.com/FedeDP)
* fix(userspace/engine): support jsonpointer escaping in rule parser [[#1777](https://github.com/falcosecurity/falco/pull/1777)] - [@jasondellaluce](https://github.com/jasondellaluce)
* fix(scripts/falco-driver-loader): support kernel object files in `.zst` and `.gz` compression formats [[#1863](https://github.com/falcosecurity/falco/pull/1863)] - [@leogr](https://github.com/leogr)
* fix(engine): correctly format json output in json_event [[#1847](https://github.com/falcosecurity/falco/pull/1847)] - [@jasondellaluce](https://github.com/jasondellaluce)
* fix: set http output content type to text/plain when json output is disabled [[#1829](https://github.com/falcosecurity/falco/pull/1829)] - [@FedeDP](https://github.com/FedeDP)
* fix(userspace/falco): accept 'Content-Type' header that contains "application/json", but it is not strictly equal to it [[#1800](https://github.com/falcosecurity/falco/pull/1800)] - [@FedeDP](https://github.com/FedeDP)
* fix(userspace/engine): supporting enabled-only overwritten rules [[#1775](https://github.com/falcosecurity/falco/pull/1775)] - [@jasondellaluce](https://github.com/jasondellaluce)
### Rule Changes
* rule(Create Symlink Over Sensitive File): corrected typo in rule output [[#1820](https://github.com/falcosecurity/falco/pull/1820)] - [@deepskyblue86](https://github.com/deepskyblue86)
* rule(macro open_write): add support to openat2 [[#1796](https://github.com/falcosecurity/falco/pull/1796)] - [@jasondellaluce](https://github.com/jasondellaluce)
* rule(macro open_read): add support to openat2 [[#1796](https://github.com/falcosecurity/falco/pull/1796)] - [@jasondellaluce](https://github.com/jasondellaluce)
* rule(macro open_directory): add support to openat2 [[#1796](https://github.com/falcosecurity/falco/pull/1796)] - [@jasondellaluce](https://github.com/jasondellaluce)
* rule(Create files below dev): add support to openat2 [[#1796](https://github.com/falcosecurity/falco/pull/1796)] - [@jasondellaluce](https://github.com/jasondellaluce)
* rule(Container Drift Detected (open+create)): add support to openat2 [[#1796](https://github.com/falcosecurity/falco/pull/1796)] - [@jasondellaluce](https://github.com/jasondellaluce)
* rule(macro sensitive_mount): add containerd socket [[#1815](https://github.com/falcosecurity/falco/pull/1815)] - [@loresuso](https://github.com/loresuso)
* rule(macro spawned_process): monitor also processes spawned by `execveat` [[#1868](https://github.com/falcosecurity/falco/pull/1868)] - [@Andreagit97](https://github.com/Andreagit97)
* rule(Create Hardlink Over Sensitive Files): new rule to detect hard links created over sensitive files [[#1810](https://github.com/falcosecurity/falco/pull/1810)] - [@sberkovich](https://github.com/sberkovich)
* rule(Detect crypto miners using the Stratum protocol): add `stratum2+tcp` and `stratum+ssl` protocols detection [[#1810](https://github.com/falcosecurity/falco/pull/1810)] - [@sberkovich](https://github.com/sberkovich)
* rule(Sudo Potential Privilege Escalation): correct special case for the CVE-2021-3156 exploit [[#1810](https://github.com/falcosecurity/falco/pull/1810)] - [@sberkovich](https://github.com/sberkovich)
* rule(list falco_hostnetwork_images): moved to k8s_audit_rules.yaml to avoid a warning when using falco_rules.yaml only [[#1681](https://github.com/falcosecurity/falco/pull/1681)] - [@leodido](https://github.com/leodido)
* rule(list deb_binaries): remove `apt-config` [[#1860](https://github.com/falcosecurity/falco/pull/1860)] - [@Andreagit97](https://github.com/Andreagit97)
* rule(Launch Remote File Copy Tools in Container): add additional binaries: curl and wget. [[#1771](https://github.com/falcosecurity/falco/pull/1771)] - [@ec4n6](https://github.com/ec4n6)
* rule(list known_sa_list): add coredns, coredns-autoscaler, endpointslicemirroring-controller, horizontal-pod-autoscaler, job-controller, node-controller (nodelifecycle), persistent-volume-binder, pv-protection-controller, pvc-protection-controller, root-ca-cert-publisher and service-account-controller as allowed service accounts in the kube-system namespace [[#1760](https://github.com/falcosecurity/falco/pull/1760)] - [@sboschman](https://github.com/sboschman)
### Non user-facing changes
* fix: force-set evt.type for plugin source events [[#1878](https://github.com/falcosecurity/falco/pull/1878)] - [@FedeDP](https://github.com/FedeDP)
* fix: updated some warning strings; properly refresh lua files embedded in falco [[#1864](https://github.com/falcosecurity/falco/pull/1864)] - [@FedeDP](https://github.com/FedeDP)
* style(userspace/engine): avoid creating multiple versions of methods only to assume default ruleset. Use a default argument instead. [[#1754](https://github.com/falcosecurity/falco/pull/1754)] - [@FedeDP](https://github.com/FedeDP)
* add raft in the adopters list [[#1776](https://github.com/falcosecurity/falco/pull/1776)] - [@teshsharma](https://github.com/teshsharma)
* build: always populate partial version variables [[#1778](https://github.com/falcosecurity/falco/pull/1778)] - [@dnwe](https://github.com/dnwe)
* build: updated cloudtrail plugin to latest version [[#1865](https://github.com/falcosecurity/falco/pull/1865)] - [@FedeDP](https://github.com/FedeDP)
* replace ".." concatenation with table.concat [[#1834](https://github.com/falcosecurity/falco/pull/1834)] - [@VadimZy](https://github.com/VadimZy)
* fix(userspace/engine): actually make m_filter_all_event_types useful by properly using it as fallback when no filter event types is provided [[#1875](https://github.com/falcosecurity/falco/pull/1875)] - [@FedeDP](https://github.com/FedeDP)
* fix(build): do not show plugin options in musl optimized builds [[#1871](https://github.com/falcosecurity/falco/pull/1871)] - [@LucaGuerra](https://github.com/LucaGuerra)
* fix(aws_cloudtrail_rules.yaml): correct required plugin versions [[#1867](https://github.com/falcosecurity/falco/pull/1867)] - [@FedeDP](https://github.com/FedeDP)
* docs: fix priority level "info" to "informational" [[#1858](https://github.com/falcosecurity/falco/pull/1858)] - [@Andreagit97](https://github.com/Andreagit97)
* Field properties changes [[#1838](https://github.com/falcosecurity/falco/pull/1838)] - [@mstemm](https://github.com/mstemm)
* update(build): updated libs to latest master version; updated plugins versions [[#1856](https://github.com/falcosecurity/falco/pull/1856)] - [@FedeDP](https://github.com/FedeDP)
* Add Giant Swarm to Adopters list [[#1842](https://github.com/falcosecurity/falco/pull/1842)] - [@stone-z](https://github.com/stone-z)
* update(tests): remove `token_bucket` unit tests [[#1798](https://github.com/falcosecurity/falco/pull/1798)] - [@jasondellaluce](https://github.com/jasondellaluce)
* fix(build): use consistent 7-character build abbrev sha [[#1830](https://github.com/falcosecurity/falco/pull/1830)] - [@LucaGuerra](https://github.com/LucaGuerra)
* add Phoenix to adopters list [[#1806](https://github.com/falcosecurity/falco/pull/1806)] - [@kaldyka](https://github.com/kaldyka)
* remove unused files in test directory [[#1801](https://github.com/falcosecurity/falco/pull/1801)] - [@jasondellaluce](https://github.com/jasondellaluce)
* drop Falco luajit module, use the one provided by libs [[#1788](https://github.com/falcosecurity/falco/pull/1788)] - [@FedeDP](https://github.com/FedeDP)
* chore(build): update libs version to 7906f7e [[#1790](https://github.com/falcosecurity/falco/pull/1790)] - [@LucaGuerra](https://github.com/LucaGuerra)
* Add SysFlow to list of libs adopters [[#1747](https://github.com/falcosecurity/falco/pull/1747)] - [@araujof](https://github.com/araujof)
* build: dropped centos8 circleci build because it is useless [[#1882](https://github.com/falcosecurity/falco/pull/1882)] - [@FedeDP](https://github.com/FedeDP)
## v0.30.0
Released on 2021-10-01
@@ -399,8 +251,8 @@ Released on 2021-01-18
* docs(proposals): Exceptions handling proposal [[#1376](https://github.com/falcosecurity/falco/pull/1376)] - [@mstemm](https://github.com/mstemm)
* docs: fix a broken link of README [[#1516](https://github.com/falcosecurity/falco/pull/1516)] - [@oke-py](https://github.com/oke-py)
* docs: adding the kubernetes privileged use case to use cases [[#1484](https://github.com/falcosecurity/falco/pull/1484)] - [@fntlnz](https://github.com/fntlnz)
* rules(Mkdir binary dirs): Adds exe_running_docker_save as an exception as this rules can be triggered when a container is created. [[#1386](https://github.com/falcosecurity/falco/pull/1386)] - [@jhwbarlow](https://github.com/jhwbarlow)
* rules(Create Hidden Files): Adds exe_running_docker_save as an exception as this rules can be triggered when a container is created. [[#1386](https://github.com/falcosecurity/falco/pull/1386)] - [@jhwbarlow](https://github.com/jhwbarlow)
* rules(Mkdir binary dirs): Adds exe_running_docker_save as an exception as this rules can be triggerred when a container is created. [[#1386](https://github.com/falcosecurity/falco/pull/1386)] - [@jhwbarlow](https://github.com/jhwbarlow)
* rules(Create Hidden Files): Adds exe_running_docker_save as an exception as this rules can be triggerred when a container is created. [[#1386](https://github.com/falcosecurity/falco/pull/1386)] - [@jhwbarlow](https://github.com/jhwbarlow)
* docs(.circleci): welcome Jonah (Amazon) as a new Falco CI maintainer [[#1518](https://github.com/falcosecurity/falco/pull/1518)] - [@leodido](https://github.com/leodido)
* build: falcosecurity/falco:master also available on the AWS ECR Public registry [[#1512](https://github.com/falcosecurity/falco/pull/1512)] - [@leodido](https://github.com/leodido)
* build: falcosecurity/falco:latest also available on the AWS ECR Public registry [[#1512](https://github.com/falcosecurity/falco/pull/1512)] - [@leodido](https://github.com/leodido)
@@ -812,7 +664,7 @@ Released on 2020-02-24
* rule(write below etc): add "dsc_host" as a ms oms program [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(write below etc): let mcafee write to /etc/cma.d [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(write below etc): let avinetworks supervisor write some ssh cfg [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(write below etc): allow writes to /etc/pki from openshift secrets dir [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(write below etc): alow writes to /etc/pki from openshift secrets dir [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(write below root): let runc write to /exec.fifo [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(change thread namespace): let cilium-cni change namespaces [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
* rule(run shell untrusted): let puma reactor spawn shells [[#1028](https://github.com/falcosecurity/falco/pull/1028)]
@@ -849,7 +701,7 @@ Released on 2020-01-23
### Bug Fixes
* fix: providing clang into docker-builder [[#972](https://github.com/falcosecurity/falco/pull/972)]
* fix: prevent throwing json type error c++ exceptions outside of the falco engine when processing k8s audit events. [[#928](https://github.com/falcosecurity/falco/pull/928)]
* fix: prevent throwing json type error c++ exceptions outside of the falco engine when procesing k8s audit events. [[#928](https://github.com/falcosecurity/falco/pull/928)]
* fix(docker/kernel/linuxkit): correct from for falco minimal image [[#913](https://github.com/falcosecurity/falco/pull/913)]
### Rule Changes
@@ -964,7 +816,7 @@ Released 2019-07-31
* Fix a problem that would cause prevent container metadata lookups when falco was daemonized [[#731](https://github.com/falcosecurity/falco/pull/731)]
* Allow rule priorities to be expressed as lowercase and a mix of lower/uppercase [[#737](https://github.com/falcosecurity/falco/pull/737)]
* Allow rule priorites to be expressed as lowercase and a mix of lower/uppercase [[#737](https://github.com/falcosecurity/falco/pull/737)]
### Rule Changes
@@ -1159,7 +1011,7 @@ Released 2019-05-13
* Docker-based builder/tester: You can now build Falco using the [falco-builder](https://falco.org/docs/source/#build-using-falco-builder-container) docker image, and run regression tests using the [falco-tester](https://falco.org/docs/source/#test-using-falco-tester-container) docker image. [[#522](https://github.com/falcosecurity/falco/pull/522)] [[#584](https://github.com/falcosecurity/falco/pull/584)]
* Several small docs changes to improve clarity and readability [[#524](https://github.com/falcosecurity/falco/pull/524)] [[#540](https://github.com/falcosecurity/falco/pull/540)] [[#541](https://github.com/falcosecurity/falco/pull/541)] [[#542](https://github.com/falcosecurity/falco/pull/542)]
* Several small docs changes to improve clarity and readibility [[#524](https://github.com/falcosecurity/falco/pull/524)] [[#540](https://github.com/falcosecurity/falco/pull/540)] [[#541](https://github.com/falcosecurity/falco/pull/541)] [[#542](https://github.com/falcosecurity/falco/pull/542)]
* Add instructions on how to enable K8s Audit Logging for kops [[#535](https://github.com/falcosecurity/falco/pull/535)]
@@ -1274,7 +1126,7 @@ Released 2019-01-16
* Fix FPs related to `apt-config`/`apt-cache`, `apk` [[#490](https://github.com/falcosecurity/falco/pull/490)]
* New rules `Launch Package Management Process in Container`, `Netcat Remote Code Execution in Container`, `Launch Suspicious Network Tool in Container` look for host-level network tools like `netcat`, package management tools like `apt-get`, or network tool binaries being run in a container. [[#490](https://github.com/falcosecurity/falco/pull/490)]
* New rules `Launch Package Management Process in Container`, `Netcat Remote Code Execution in Container`, `Lauch Suspicious Network Tool in Container` look for host-level network tools like `netcat`, package management tools like `apt-get`, or network tool binaries being run in a container. [[#490](https://github.com/falcosecurity/falco/pull/490)]
* Fix the `inbound` and `outbound` macros so they work with sendto/recvfrom/sendmsg/recvmsg. [[#470](https://github.com/falcosecurity/falco/pull/470)]
@@ -1307,7 +1159,7 @@ Released 2018-11-09
* Better coverage (e.g. reduced FPs) for critical stack, hids systems, ufw, cloud-init, etc. [[#445](https://github.com/falcosecurity/falco/pull/445)]
* New rules `Launch Package Management Process in Container`, `Netcat Remote Code Execution in Container`, and `Launch Suspicious Network Tool in Container` look for running various suspicious programs in a container. [[#461](https://github.com/falcosecurity/falco/pull/461)]
* New rules `Launch Package Management Process in Container`, `Netcat Remote Code Execution in Container`, and `Lauch Suspicious Network Tool in Container` look for running various suspicious programs in a container. [[#461](https://github.com/falcosecurity/falco/pull/461)]
* Misc changes to address false positives in GKE, Istio, etc. [[#455](https://github.com/falcosecurity/falco/pull/455)] [[#439](https://github.com/falcosecurity/falco/issues/439)]
@@ -1362,7 +1214,7 @@ Released 2018-07-24
### Minor Changes
* Rules may now have an `skip-if-unknown-filter` property. If set to true, a rule will be skipped if its condition/output property refers to a filtercheck (e.g. `fd.some-new-attribute`) that is not present in the current falco version. [[#364](https://github.com/draios/falco/pull/364)] [[#345](https://github.com/draios/falco/issues/345)]
* Rules may now have an `skip-if-unknown-filter` property. If set to true, a rule will be skipped if its condition/output property refers to a filtercheck (e.g. `fd.some-new-attibute`) that is not present in the current falco version. [[#364](https://github.com/draios/falco/pull/364)] [[#345](https://github.com/draios/falco/issues/345)]
* Small changes to Falco `COPYING` file so github automatically recognizes license [[#380](https://github.com/draios/falco/pull/380)]
* New example integration showing how to connect Falco with Anchore to dynamically create falco rules based on negative scan results [[#390](https://github.com/draios/falco/pull/390)]
* New example integration showing how to connect Falco, [nats](https://nats.io/), and K8s to run flexible "playbooks" based on Falco events [[#389](https://github.com/draios/falco/pull/389)]
@@ -1463,7 +1315,7 @@ Released 2017-10-10
Released 2017-10-10
**Important**: the location for falco's configuration file has moved from `/etc/falco.yaml` to `/etc/falco/falco.yaml`. The default rules file has moved from `/etc/falco_rules.yaml` to `/etc/falco/falco_rules.yaml`. In addition, 0.8.0 has added a _local_ rules file to `/etc/falco/falco_rules.local.yaml`. See [the documentation](https://github.com/draios/falco/wiki/Falco-Default-and-Local-Rules-Files) for more details.
**Important**: the location for falco's configuration file has moved from `/etc/falco.yaml` to `/etc/falco/falco.yaml`. The default rules file has moved from `/etc/falco_rules.yaml` to `/etc/falco/falco_rules.yaml`. In addition, 0.8.0 has added a _local_ ruls file to `/etc/falco/falco_rules.local.yaml`. See [the documentation](https://github.com/draios/falco/wiki/Falco-Default-and-Local-Rules-Files) for more details.
### Major Changes

View File

@@ -58,7 +58,6 @@ if(CMAKE_BUILD_TYPE STREQUAL "debug")
else()
set(CMAKE_BUILD_TYPE "release")
set(KBUILD_FLAGS "${DRAIOS_FEATURE_FLAGS}")
add_definitions(-DBUILD_TYPE_RELEASE)
endif()
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
@@ -68,7 +67,6 @@ endif()
if(MUSL_OPTIMIZED_BUILD)
set(MUSL_FLAGS "-static -Os -fPIE -pie")
add_definitions(-DMUSL_OPTIMIZED)
endif()
# explicitly set hardening flags
@@ -99,8 +97,8 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O3 -fno-strict-aliasing -DNDEBUG")
include(GetFalcoVersion)
set(PACKAGE_NAME "falco")
set(DRIVER_NAME "falco")
set(DRIVER_DEVICE_NAME "falco")
set(PROBE_NAME "falco")
set(PROBE_DEVICE_NAME "falco")
set(DRIVERS_REPO "https://download.falco.org/driver")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX
@@ -115,6 +113,9 @@ include(ExternalProject)
# libs
include(falcosecurity-libs)
# LuaJit provided by libs
include(luajit)
# jq
include(jq)
@@ -147,7 +148,14 @@ if(NOT MINIMAL_BUILD)
include(civetweb)
endif()
include(cxxopts)
# Lpeg
include(lpeg)
# libyaml
include(libyaml)
# lyaml
include(lyaml)
# One TBB
include(tbb)
@@ -198,9 +206,7 @@ add_subdirectory(userspace/engine)
add_subdirectory(userspace/falco)
add_subdirectory(tests)
if(NOT MUSL_OPTIMIZED_BUILD)
include(plugins)
endif()
include(plugins)
# Packages configuration
include(CPackConfig)

View File

@@ -5,7 +5,7 @@
[![Build Status](https://img.shields.io/circleci/build/github/falcosecurity/falco/master?style=for-the-badge)](https://circleci.com/gh/falcosecurity/falco) [![CII Best Practices Summary](https://img.shields.io/cii/summary/2317?label=CCI%20Best%20Practices&style=for-the-badge)](https://bestpractices.coreinfrastructure.org/projects/2317) [![GitHub](https://img.shields.io/github/license/falcosecurity/falco?style=for-the-badge)](COPYING)
Want to talk? Join us on the [#falco](https://kubernetes.slack.com/messages/falco) channel in the [Kubernetes Slack](https://slack.k8s.io).
Want to talk? Join us on the [#falco](https://kubernetes.slack.com/archives/CMWH3EH32) channel in the [Kubernetes Slack](https://slack.k8s.io).
### Latest releases
@@ -58,25 +58,10 @@ Notes:
---
The Falco Project, originally created by [Sysdig](https://sysdig.com), is an incubating [CNCF](https://cncf.io) open source cloud native runtime security tool.
Falco makes it easy to consume kernel events, and enrich those events with information from Kubernetes and the rest of the cloud native stack.
Falco can also be extended to other data sources by using plugins.
Falco makes it easy to consume kernel events, and enrich those events with information from Kubernetes and the rest of the cloud native stack.
Falco has a rich set of security rules specifically built for Kubernetes, Linux, and cloud-native.
If a rule is violated in a system, Falco will send an alert notifying the user of the violation and its severity.
### What can Falco detect?
Falco can detect and alert on any behavior that involves making Linux system calls.
Falco alerts can be triggered by the use of specific system calls, their arguments, and by properties of the calling process.
For example, Falco can easily detect incidents including but not limited to:
- A shell is running inside a container or pod in Kubernetes.
- A container is running in privileged mode, or is mounting a sensitive path, such as `/proc`, from the host.
- A server process is spawning a child process of an unexpected type.
- Unexpected read of a sensitive file, such as `/etc/shadow`.
- A non-device file is written to `/dev`.
- A standard system binary, such as `ls`, is making an outbound network connection.
- A privileged pod is started in a Kubernetes cluster.
### Installing Falco
If you would like to run Falco in **production** please adhere to the [official installation guide](https://falco.org/docs/getting-started/installation/).
@@ -105,22 +90,20 @@ The Falco Project supports various SDKs for this endpoint.
| Rust | [client-rs](https://github.com/falcosecurity/client-rs) |
| Python | [client-py](https://github.com/falcosecurity/client-py) |
### Plugins
Falco comes with a [plugin framework](https://falco.org/docs/plugins/) that extends it to potentially any cloud detection scenario. Plugins are shared libraries that conform to a documented API and allow for:
### What can Falco detect?
- Adding new event sources that can be used in rules;
- Adding the ability to define new fields and extract information from events.
The Falco Project maintains [various plugins](https://github.com/falcosecurity/plugins) and provides SDKs for plugin development.
##### SDKs
| Language | Repository |
|----------|-------------------------------------------------------------------------------|
| Go | [falcosecurity/plugin-sdk-go](https://github.com/falcosecurity/plugin-sdk-go) |
Falco can detect and alert on any behavior that involves making Linux system calls.
Falco alerts can be triggered by the use of specific system calls, their arguments, and by properties of the calling process.
For example, Falco can easily detect incidents including but not limited to:
- A shell is running inside a container or pod in Kubernetes.
- A container is running in privileged mode, or is mounting a sensitive path, such as `/proc`, from the host.
- A server process is spawning a child process of an unexpected type.
- Unexpected read of a sensitive file, such as `/etc/shadow`.
- A non-device file is written to `/dev`.
- A standard system binary, such as `ls`, is making an outbound network connection.
- A privileged pod is started in a Kubernetes cluster.
### Documentation
@@ -132,7 +115,7 @@ To get involved with The Falco Project please visit [the community repository](h
How to reach out?
- Join the [#falco](https://kubernetes.slack.com/messages/falco) channel on the [Kubernetes Slack](https://slack.k8s.io)
- Join the #falco channel on the [Kubernetes Slack](https://slack.k8s.io)
- [Join the Falco mailing list](https://lists.cncf.io/g/cncf-falco-dev)
- [Read the Falco documentation](https://falco.org/docs/)

View File

@@ -95,7 +95,7 @@ Now assume `x.y.z` is the new version.
#### Release Manager <github handle>
<!-- Substitute GitHub handle with the release manager's one -->
<!-- Substitute Github handle with the release manager's one -->
```
- Finally, publish the release!

View File

@@ -5,7 +5,7 @@
This document describes The Falco Project's branding guidelines, language, and message.
Content in this document can be used to publicly share about Falco.
Content in this document can be used to publically share about Falco.
@@ -56,7 +56,7 @@ If a rule has been violated, Falco triggers an alert.
### How does Falco work?
Falco traces kernel events and reports information about the system calls being executed at runtime.
Falco leverages the extended berkeley packet filter (eBPF) which is a kernel feature implemented for dynamic crash-resilient and secure code execution in the kernel.
Falco leverages the extended berkley packet filter (eBPF) which is a kernel feature implemented for dynamic crash-resilient and secure code execution in the kernel.
Falco enriches these kernel events with information about containers running on the system.
Falco also can consume signals from other input streams such as the containerd socket, the Kubernetes API server and the Kubernetes audit log.
At runtime, Falco will reason about these events and assert them against configured security rules.
@@ -113,7 +113,7 @@ Falco ultimately is a security engine. It reasons about signals coming from a sy
##### Anomaly detection
This refers to an event that occurs with something unusual, concerning, or odd occurs.
This refers to an event that occurs with something unsual, concerning, or odd occurs.
We can associate anomalies with unwanted behavior, and alert in their presence.
##### Detection tooling
@@ -143,10 +143,6 @@ Sometimes this word is incorrectly used to refer to a `probe`.
The global term for the software that sends events from the kernel. Such as the eBPF `probe` or the `kernel module`.
#### Plugin
Used to describe a dynamic shared library (`.so` files in Unix, `.dll` files in Windows) that conforms to a documented API and allows to extend Falco's capabilities.
#### Falco
The name of the project, and also the name of [the main engine](https://github.com/falcosecurity/falco) that the rest of the project is built on.

View File

@@ -10,4 +10,5 @@ endif()
if(CPACK_GENERATOR MATCHES "TGZ")
set(CPACK_SET_DESTDIR "ON")
set(CPACK_STRIP_FILES "OFF")
endif()

View File

@@ -1,3 +1,4 @@
/etc/falco/falco.yaml
/etc/falco/falco_rules.yaml
/etc/falco/rules.available/application_rules.yaml
/etc/falco/falco_rules.local.yaml

View File

@@ -45,7 +45,7 @@ string(REPLACE "\n" ";" output "${output}")
# Parse output
foreach(line ${output})
set(test ${line})
# use escape commas to handle properly test cases with commands inside the name
# use escape commas to handle properly test cases with commans inside the name
string(REPLACE "," "\\," test_name ${test})
# ...and add to script
add_command(add_test "${prefix}${test}${suffix}" ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" "${test_name}" ${extra_args})

View File

@@ -14,8 +14,8 @@ include(ExternalProject)
set(CATCH2_INCLUDE ${CMAKE_BINARY_DIR}/catch2-prefix/include)
set(CATCH_EXTERNAL_URL URL https://github.com/catchorg/catch2/archive/v2.13.9.tar.gz URL_HASH
SHA256=06dbc7620e3b96c2b69d57bf337028bf245a211b3cddb843835bfe258f427a52)
set(CATCH_EXTERNAL_URL URL https://github.com/catchorg/catch2/archive/v2.12.1.tar.gz URL_HASH
SHA256=e5635c082282ea518a8dd7ee89796c8026af8ea9068cd7402fb1615deacd91c3)
ExternalProject_Add(
catch2

View File

@@ -24,7 +24,6 @@ ExternalProject_Add(
GIT_TAG "v1.4.0"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
UPDATE_COMMAND ""
INSTALL_COMMAND
${CMAKE_COMMAND} -E copy ${STRING_VIEW_LITE_PREFIX}/src/string-view-lite/include/nonstd/string_view.hpp
${STRING_VIEW_LITE_INCLUDE}/nonstd/string_view.hpp)

View File

@@ -12,8 +12,8 @@
#
set(CIVETWEB_SRC "${PROJECT_BINARY_DIR}/civetweb-prefix/src/civetweb/")
set(CIVETWEB_LIB "${CIVETWEB_SRC}/install/lib/libcivetweb.a")
SET(CIVETWEB_CPP_LIB "${CIVETWEB_SRC}/install/lib/libcivetweb-cpp.a")
set(CIVETWEB_LIB "${CIVETWEB_SRC}/install/${CMAKE_INSTALL_LIBDIR}/libcivetweb.a")
SET(CIVETWEB_CPP_LIB "${CIVETWEB_SRC}/install/${CMAKE_INSTALL_LIBDIR}/libcivetweb-cpp.a")
set(CIVETWEB_INCLUDE_DIR "${CIVETWEB_SRC}/install/include")
message(STATUS "Using bundled civetweb in '${CIVETWEB_SRC}'")
if (USE_BUNDLED_OPENSSL)
@@ -25,7 +25,6 @@ if (USE_BUNDLED_OPENSSL)
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCMAKE_INSTALL_LIBDIR=lib
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off

View File

@@ -1,30 +0,0 @@
#
# Copyright (C) 2022 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.
#
function(copy_files_to_build_dir source_files targetsuffix)
set(build_files)
foreach(file_path ${source_files})
get_filename_component(trace_file ${file_path} NAME)
list(APPEND build_files ${CMAKE_CURRENT_BINARY_DIR}/${trace_file})
endforeach()
add_custom_target(copy-files-${targetsuffix} ALL
DEPENDS ${build_files})
add_custom_command(OUTPUT ${build_files}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${source_files} ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${source_files})
endfunction()

View File

@@ -24,8 +24,8 @@ else()
# default below In case you want to test against another falcosecurity/libs version just pass the variable - ie., `cmake
# -DFALCOSECURITY_LIBS_VERSION=dev ..`
if(NOT FALCOSECURITY_LIBS_VERSION)
set(FALCOSECURITY_LIBS_VERSION "b19f87e8aee663e4987a3db54570725e071ed105")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=fd5888588796bf52848cf75434784770e61d9a79f344bf571fe495b61e92ddd3")
set(FALCOSECURITY_LIBS_VERSION "bb9bee8e522fc953c2a79093d688d3d82b925e8b")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=ab2f18ff9c8d92dd06088ccfa73d4230fce3617613229f5afd839a37c13b0459")
endif()
# cd /path/to/build && cmake /path/to/source
@@ -47,8 +47,8 @@ if(MUSL_OPTIMIZED_BUILD)
add_definitions(-DMUSL_OPTIMIZED)
endif()
set(DRIVER_VERSION "${FALCOSECURITY_LIBS_VERSION}")
set(DRIVER_NAME "falco")
set(PROBE_VERSION "${FALCOSECURITY_LIBS_VERSION}")
set(PROBE_NAME "falco")
set(DRIVER_PACKAGE_NAME "falco")
set(SCAP_BPF_PROBE_ENV_VAR_NAME "FALCO_BPF_PROBE")
set(SCAP_HOST_ROOT_ENV_VAR_NAME "HOST_ROOT")
@@ -62,9 +62,14 @@ set(LIBSINSP_DIR "${FALCOSECURITY_LIBS_SOURCE_DIR}")
set(CREATE_TEST_TARGETS OFF CACHE BOOL "")
set(BUILD_LIBSCAP_EXAMPLES OFF CACHE BOOL "")
# todo(leogr): although Falco does not actually depend on chisels, we need this for the lua_parser.
# Hopefully, we can switch off this in the future
set(WITH_CHISEL ON CACHE BOOL "")
set(USE_BUNDLED_TBB ON CACHE BOOL "")
set(USE_BUNDLED_B64 ON CACHE BOOL "")
set(USE_BUNDLED_JSONCPP ON CACHE BOOL "")
set(USE_BUNDLED_LUAJIT ON CACHE BOOL "")
list(APPEND CMAKE_MODULE_PATH "${FALCOSECURITY_LIBS_SOURCE_DIR}/cmake/modules")

28
cmake/modules/lpeg.cmake Normal file
View File

@@ -0,0 +1,28 @@
#
# Copyright (C) 2020 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
set(LPEG_SRC "${PROJECT_BINARY_DIR}/lpeg-prefix/src/lpeg")
set(LPEG_LIB "${PROJECT_BINARY_DIR}/lpeg-prefix/src/lpeg/build/lpeg.a")
message(STATUS "Using bundled lpeg in '${LPEG_SRC}'")
set(LPEG_DEPENDENCIES "")
list(APPEND LPEG_DEPENDENCIES "luajit")
ExternalProject_Add(
lpeg
DEPENDS ${LPEG_DEPENDENCIES}
URL "http://www.inf.puc-rio.br/~roberto/lpeg/lpeg-1.0.2.tar.gz"
URL_HASH "SHA256=48d66576051b6c78388faad09b70493093264588fcd0f258ddaab1cdd4a15ffe"
BUILD_COMMAND LUA_INCLUDE=${LUAJIT_INCLUDE} "${PROJECT_SOURCE_DIR}/scripts/build-lpeg.sh" "${LPEG_SRC}/build"
BUILD_IN_SOURCE 1
BUILD_BYPRODUCTS ${LPEG_LIB}
CONFIGURE_COMMAND ""
INSTALL_COMMAND "")

28
cmake/modules/lyaml.cmake Normal file
View File

@@ -0,0 +1,28 @@
#
# Copyright (C) 2020 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
set(LYAML_ROOT "${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml")
set(LYAML_LIB "${LYAML_ROOT}/ext/yaml/.libs/yaml.a")
set(LYAML_LUA_DIR "${LYAML_ROOT}/lib")
message(STATUS "Using bundled lyaml in '${LYAML_ROOT}'")
externalproject_add(
lyaml
DEPENDS luajit libyaml
URL "https://github.com/gvvaughan/lyaml/archive/release-v6.0.tar.gz"
URL_HASH "SHA256=9d7cf74d776999ff6f758c569d5202ff5da1f303c6f4229d3b41f71cd3a3e7a7"
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
BUILD_BYPRODUCTS ${LYAML_LIB}
INSTALL_COMMAND ""
CONFIGURE_COMMAND ./configure --enable-static CFLAGS=-I${LIBYAML_INSTALL_DIR}/include CPPFLAGS=-I${LIBYAML_INSTALL_DIR}/include LDFLAGS=-L${LIBYAML_INSTALL_DIR}/lib LIBS=-lyaml LUA=${LUAJIT_SRC}/luajit LUA_INCLUDE=-I${LUAJIT_INCLUDE}
)

View File

@@ -13,26 +13,22 @@
include(ExternalProject)
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} PLUGINS_SYSTEM_NAME)
set(PLUGINS_VERSION "0.1.0-rc1-28-g019437e")
# todo(jasondellaluce): switch this to a stable version once this plugin gets
# released with a 1.0.0 required plugin api version
ExternalProject_Add(
cloudtrail-plugin
URL "https://download.falco.org/plugins/dev/cloudtrail-0.2.5-0.2.5-8%2B2c1bb25-${PLUGINS_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=eeefbeb639e41e37cd864042d8b4854e0af451e6b8b34a14c39332771e94ee5b"
URL "https://download.falco.org/plugins/dev/cloudtrail-${PLUGINS_VERSION}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=ad9692957c5435238e07d1625e1b247eabe98b85f54de9218367fdd73a6f3f0b"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
install(FILES "${PROJECT_BINARY_DIR}/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so" DESTINATION "${FALCO_PLUGINS_DIR}")
# todo(jasondellaluce): switch this to a stable version once this plugin gets
# released with a 1.0.0 required plugin api version
ExternalProject_Add(
json-plugin
URL "https://download.falco.org/plugins/dev/json-0.2.2-0.2.2-24%2B2c1bb25-${PLUGINS_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=181ac01d11defee24ad5947fcd836e256e13c61ca9475253fb82b60297164748"
URL "https://download.falco.org/plugins/dev/json-${PLUGINS_VERSION}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=721ea5226b0f623915d0d5c34870589ad33a8ff795b0daa1af72f21a67430077"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")

View File

@@ -7,11 +7,10 @@ This directory contains various ways to package Falco as a container and related
| Name | Directory | Description |
|---|---|---|
| [falcosecurity/falco:latest](https://hub.docker.com/repository/docker/falcosecurity/falco), [falcosecurity/falco:_tag_](https://hub.docker.com/repository/docker/falcosecurity/falco), [falcosecurity/falco:master](https://hub.docker.com/repository/docker/falcosecurity/falco) | docker/falco | Falco (DEB built from git tag or from the master) with all the building toolchain. |
| _not yet published (experimental)_ | docker/ubi | Falco (built from RedHat's UBI base image) with the building toolchain. |
| [falcosecurity/falco-driver-loader:latest](https://hub.docker.com/repository/docker/falcosecurity/falco-driver-loader), [falcosecurity/falco-driver-loader:_tag_](https://hub.docker.com/repository/docker/falcosecurity/falco-driver-loader), [falcosecurity/falco-driver-loader:master](https://hub.docker.com/repository/docker/falcosecurity/falco-driver-loader) | docker/driver-loader | `falco-driver-loader` as entrypoint with the building toolchain. |
| [falcosecurity/falco-no-driver:latest](https://hub.docker.com/repository/docker/falcosecurity/falco-no-driver), [falcosecurity/falco-no-driver:_tag_](https://hub.docker.com/repository/docker/falcosecurity/falco-no-driver),[falcosecurity/falco-no-driver:master](https://hub.docker.com/repository/docker/falcosecurity/falco-no-driver) | docker/no-driver | Falco (TGZ built from git tag or from the master) without the building toolchain. |
| [falcosecurity/falco-builder:latest](https://hub.docker.com/repository/docker/falcosecurity/falco-builder) | docker/builder | The complete build tool chain for compiling Falco from source. See [the documentation](https://falco.org/docs/getting-started/source/) for more details on building from source. Used to build Falco (CI). |
| [falcosecurity/falco-tester:latest](https://hub.docker.com/repository/docker/falcosecurity/falco-tester) | docker/tester | Container image for running the Falco test suite. Used to run Falco integration tests (CI). |
| _not to be published_ | docker/local | Built on-the-fly and used by falco-tester. |
| _to not be published_ | docker/local | Built on-the-fly and used by falco-tester. |
> Note: `falco-builder`, `falco-tester` (and the `docker/local` image that it's built on the fly) are not integrated into the release process because they are development and CI tools that need to be manually pushed only when updated.

View File

@@ -1,5 +1,5 @@
# IMPORTANT: Do not add more content to this file unless you know what you are doing.
# This file is sourced every time the shell session is opened.
# This file is sourced everytime the shell session is opened.
#
# This will make scl collection binaries work out of box.
unset BASH_ENV PROMPT_COMMAND ENV

View File

@@ -1,7 +1,13 @@
include(copy_files_to_build_dir)
# Note: list of rules is created at cmake time, not build time
file(GLOB test_rule_files
"${CMAKE_CURRENT_SOURCE_DIR}/../../../test/rules/*.yaml")
copy_files_to_build_dir("${test_rule_files}" docker-local-rules)
foreach(rule_file_path ${test_rule_files})
get_filename_component(rule_file ${rule_file_path} NAME)
add_custom_target(docker-local-rule-${rule_file} ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${rule_file})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${rule_file}
COMMAND ${CMAKE_COMMAND} -E copy ${rule_file_path} ${CMAKE_CURRENT_BINARY_DIR}/${rule_file}
DEPENDS ${rule_file_path})
endforeach()

View File

@@ -1,7 +1,13 @@
include(copy_files_to_build_dir)
# Note: list of traces is created at cmake time, not build time
file(GLOB test_trace_files
"${CMAKE_CURRENT_SOURCE_DIR}/../../../test/trace_files/*.scap")
copy_files_to_build_dir("${test_trace_files}" docker-local-traces)
foreach(trace_file_path ${test_trace_files})
get_filename_component(trace_file ${trace_file_path} NAME)
add_custom_target(docker-local-trace-${trace_file} ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${trace_file})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${trace_file}
COMMAND ${CMAKE_COMMAND} -E copy ${trace_file_path} ${CMAKE_CURRENT_BINARY_DIR}/${trace_file}
DEPENDS ${trace_file_path})
endforeach()

View File

@@ -20,7 +20,7 @@ RUN curl -L -o falco.tar.gz \
RUN sed -e 's/time_format_iso_8601: false/time_format_iso_8601: true/' < /falco/etc/falco/falco.yaml > /falco/etc/falco/falco.yaml.new \
&& mv /falco/etc/falco/falco.yaml.new /falco/etc/falco/falco.yaml
FROM debian:11-slim
FROM scratch
LABEL maintainer="cncf-falco-dev@lists.cncf.io"

View File

@@ -1,45 +0,0 @@
ARG UBI_VERSION=latest
FROM registry.access.redhat.com/ubi8/ubi:${UBI_VERSION}
ARG FALCO_VERSION
RUN test -n "$FALCO_VERSION" || (echo "FALCO_VERSION not set" && false)
ENV FALCO_VERSION=${FALCO_VERSION}
LABEL "name"="Falco Runtime Security"
LABEL "vendor"="Falco"
LABEL "version"="${FALCO_VERSION}"
LABEL "release"="${FALCO_VERSION}"
LABEL "ubi-version"="${UBI_VERSION}"
LABEL "summary"="Falco is a security policy engine that monitors system calls and cloud events, and fires alerts when security policies are violated."
LABEL "description"="Falco is a security policy engine that monitors system calls and cloud events, and fires alerts when security policies are violated."
LABEL "io.k8s.display-name"="Falco"
LABEL "io.k8s.description"="Falco is a security policy engine that monitors system calls and cloud events, and fires alerts when security policies are violated."
LABEL maintainer="cncf-falco-dev@lists.cncf.io"
LABEL usage="docker run -i -t --privileged -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro -v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro -v /etc:/host/etc --name NAME IMAGE"
ENV HOST_ROOT /host
ENV HOME /root
RUN dnf -y update && \
dnf -y install \
curl \
make \
cmake \
gcc \
llvm-toolset \
clang \
kmod \
&& dnf -y clean all ; rm -rf /var/cache/{dnf,yum}
RUN mkdir /build && cd /build/ && curl --remote-name-all -L https://github.com/dell/dkms/archive/refs/tags/v3.0.3.tar.gz && \
tar xvf v3.0.3.tar.gz && cd dkms-3.0.3 && make install-redhat && rm -rf /build
RUN mkdir /deploy && cd /deploy/ && curl --remote-name-all -L https://download.falco.org/packages/bin/x86_64/falco-${FALCO_VERSION}-x86_64.tar.gz && \
cd / && tar --strip-components=1 -xvf /deploy/falco-${FALCO_VERSION}-x86_64.tar.gz && \
rm -rf /deploy
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["/usr/bin/falco"]

View File

@@ -1,39 +0,0 @@
#!/bin/bash
#
# Copyright (C) 2022 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Set the SKIP_DRIVER_LOADER variable to skip loading the driver
if [[ -z "${SKIP_DRIVER_LOADER}" ]]; then
# Required by dkms to find the required dependencies on RedHat UBI
rm -fr /usr/src/kernels/ && rm -fr /usr/src/debug/
rm -fr /lib/modules && ln -s $HOST_ROOT/lib/modules /lib/modules
rm -fr /boot && ln -s $HOST_ROOT/boot /boot
echo "* Setting up /usr/src links from host"
for i in "$HOST_ROOT/usr/src"/*
do
base=$(basename "$i")
ln -s "$i" "/usr/src/$base"
done
/usr/bin/falco-driver-loader
fi
exec "$@"

View File

@@ -92,7 +92,7 @@ log_level: info
# Minimum rule priority level to load and run. All rules having a
# priority more severe than this level will be loaded/run. Can be one
# of "emergency", "alert", "critical", "error", "warning", "notice",
# "informational", "debug".
# "info", "debug".
priority: debug
# Whether or not output to any of the output channels below is
@@ -246,7 +246,6 @@ program_output:
http_output:
enabled: false
url: http://some.url
user_agent: "falcosecurity/falco"
# Falco supports running a gRPC server with two main binding types
# 1. Over the network with mandatory mutual TLS authentication (mTLS)

View File

@@ -47,7 +47,7 @@ The motivation behind this proposal is to design a new output implementation tha
### Non-Goals
- To substitute existing outputs (stdout, syslog, etc.)
- To support different queuing systems than the default (round-robin) one
- To support different queing systems than the default (round-robin) one
- To support queuing mechanisms for message retransmission
- Users can have a local gRPC relay server along with Falco that multiplexes connections and handles retires and backoff
- To change the output format

View File

@@ -27,7 +27,7 @@ That's where Falco comes in. We want to make it possible for Falco to perform a
Transparently read a candidate PSP into an equivalent set of Falco rules that can look for the conditions in the PSP.
The PSP is converted into a set of Falco rules which can be either saved as a file for later use/inspection, or loaded directly so that they can monitor system calls and k8s audit activity.
The PSP is converted into a set of Falco rules which can be either saved as a file for later use/inspection, or loaded directly so they they can monitor system calls and k8s audit activity.
### Non-Goals
@@ -51,6 +51,6 @@ No diagrams yet.
* We'll use [inja](https://github.com/pantor/inja) as the templating engine.
* For the most part, we can rely on the existing framework of rules, filter expressions, and output expressions that already exist in Falco. One significant change will be that filter fields can extract more than one "value" per event, and we'll need to define new operators to perform set comparisons between values in an event and values in the comparison right-hand-side.
* For the most part, we can rely on the existing framework of rules, filter expressions, and output expressions that already exist in Falco. One significant change will be that filter fields can extract more than one "value" per event, and we'll need to define new operators to perform set comparisions betweeen values in an event and values in the comparison right-hand-side.
* This will rely heavily on existing support for [K8s Audit Events](https://falco.org/docs/event-sources/kubernetes-audit/) in Falco.

View File

@@ -6,13 +6,13 @@ This is a proposal to better structure the Falco API.
The Falco API is a set of contracts describing how users can interacts with Falco.
By defining a set of interfaces the Falco Authors intend to decouple Falco from other software and data (eg., from the input sources) and, at the same time, make it more extensible.
By definiing a set of interfaces the Falco Authors intend to decouple Falco from other softwares and data (eg., from the input sources) and, at the same time, make it more extensible.
Thus, this document intent is to propose a list of services that constitute the Falco API (targeting the first stable version of Falco, v1.0.0).
Thus, this document intent is to propose a list of services that contistute the Falco API (targeting the first stable version of Falco, v1.0.0).
## Motivation
We want to enable users to use third-party clients to interface with Falco outputs, inputs, rules, and configurations.
We want to enable users to use thirdy-party clients to interface with Falco outputs, inputs, rules, and configurations.
Such ability would enable the community to create a whole set of OSS tools, built on top of Falco.
@@ -94,7 +94,7 @@ This translates in having the following set of `proto` files.
}
```
- one or more `.proto` containing the command models - ie., the already existing `schema.proto` containing source enum, etc.
- one or more `.proto` containing the commond models - ie., the already existing `schema.proto` containing source enum, etc.
```proto3
# schema.proto

View File

@@ -36,7 +36,7 @@ There will be no intention to cover Falco rule syntax in this proposal.
### Use cases
When new PRs are created in the area of rules, reviewers need to examine whether there are new rules, macros or lists are introduced. If yes, check whether follow the naming convention.
When new PRs are created in the area of rules, reviewers need to examine whether there are new rules, macros or lists are introduced. If yes, check wether follow the naming convention.
### Diagrams

View File

@@ -82,7 +82,7 @@ This is done as needed, and can best be measured by the need to cut a release an
### official support
As the need for a project grows, it can ultimately achieve the highest and most coveted status within The Falco Project. "_Official support_."
As the need for a project grows, it can ultimately achieve the highest and most coveted status within The Falco Project. "_Offical support_."
The artifacts listed above are part of the official Falco release process. These artifact will be refined and amended by the [Part 2](./20200506-artifacts-scope-part-2.md).
@@ -111,4 +111,4 @@ Update documentation in [falco-website#184](https://github.com/falcosecurity/fal
### Adjusting projects
- YAML manifest documentation to be moved to `contrib`
- Minikube, Kind, Puppet, Ansible, etc documentation to be moved to `contrib`
- Minkube, Kind, Puppet, Ansible, etc documentation to be moved to `contrib`

View File

@@ -196,13 +196,13 @@ Exception values will most commonly be defined in rules with append: true. Here'
A rule exception applies if for a given event, the fields in a rule.exception match all of the values in some exception.item. For example, if a program `apk` writes to a file below `/usr/lib/alpine`, the rule will not trigger, even if the condition is met.
Notice that an item in a values list can be a list. This allows building exceptions with operators like "in", "pmatch", etc. that work on a list of items. The item can also be a name of an existing list. If not present surrounding parentheses will be added.
Notice that an item in a values list can be a list. This allows building exceptions with operators like "in", "pmatch", etc. that work on a list of items. The item can also be a name of an existing list. If not present surrounding parantheses will be added.
Finally, note that the structure of the values property differs between the items where fields is a list of fields (proc_writer/container_writer/proc_filenames) and when it is a single field (procs_only). This changes how the condition snippet is constructed.
### Implementation
For exception items where the fields property is a list of field names, each exception can be thought of as an implicit "and not (field1 cmp1 val1 and field2 cmp2 val2 and...)" appended to the rule's condition. For exception items where the fields property is a single field name, the exception can be thought of as an implicit "and not field cmp (val1, val2, ...)". In practice, that's how exceptions will be implemented.
For exception items where the fields property is a list of field names, each exception can be thought of as an implicit "and not (field1 cmp1 val1 and field2 cmp2 val2 and...)" appended to the rule's condition. For exception items where the fields property is a single field name, the exception can be thought of as an implict "and not field cmp (val1, val2, ...)". In practice, that's how exceptions will be implemented.
When a rule is parsed, the original condition will be wrapped in an extra layer of parentheses and all exception values will be appended to the condition. For example, using the example above, the resulting condition will be:
@@ -214,7 +214,7 @@ When a rule is parsed, the original condition will be wrapped in an extra layer
(fd.filename in (python, go))))
```
The exceptions are effectively syntactic sugar that allows expressing sets of exceptions in a concise way.
The exceptions are effectively syntatic sugar that allows expressing sets of exceptions in a concise way.
### Advantages

View File

@@ -2,7 +2,7 @@
This document reflects when and how we clean up the Falco artifacts from their storage location.
**Superseded by**: [drivers-storage-s3 proposal](https://github.com/falcosecurity/falco/blob/master/proposals/20201025-drivers-storage-s3.md).
**Superseeded by**: [drivers-storage-s3 proposal](https://github.com/falcosecurity/falco/blob/master/proposals/20201025-drivers-storage-s3.md).
## Motivation
@@ -90,7 +90,7 @@ This way, assuming the number of prebuilt drivers does not skyrocket, we can rea
Notice that, in case a Falco stable release will not depend on a new driver version, this means the last two driver versions will, in this case, cover more than the two Falco stable releases.
### Archiving
### Archivation
Since the process of building drivers is time and resource consuming, this document also proposes to move the driver versions in other storage facilities.

View File

@@ -66,7 +66,7 @@ Source plugins also provide an "id", which is globally unique and is used in cap
An extractor plugin focuses only on field extraction from events generated by other plugins, or by the core libraries. It does *not* provide an event source, but can extract fields from other event sources. An example is json field extraction, where a plugin might be able to extract fields from arbitrary json payloads.
An extractor plugin provides an optional set of event sources. When the framework receives an event with an event source in the plugin's set of event sources, fields in expressions/Falco outputs will be extracted from events using the plugin. An extractor plugin can also *not* name a set of event sources. In this case, fields will be extracted from *all* events, regardless of source. In this case, the extractor plugin must detect the format of arbitrary payloads and be able to return NULL/no value when the payload is not supported.
An extractor plugin provides an optional set of event sources. When the framework receives an event with an event source in the plugin's set of event sources, fields in expressions/Falco outputs will be extracted from events using the plugin. An extractor plugin can also *not* name a set of event sources. In this case, fields will be extracted from *all* events, regardless of source. In this case, the exctractor plugin must detect the format of arbitrary payloads and be able to return NULL/no value when the payload is not supported.
### Support for Plugin Events in Capture Files.
@@ -91,7 +91,7 @@ The libraries will do everything possible to validate the data coming from the p
### Plugin/Event Source registries
Every source plugin requires its own, unique plugin ID to interoperate with Falco and the other plugins. The plugin ID will be used by the libs to properly process incoming events (for example, when saving events to file and loading them back), and by plugins to unambiguously recognize their dependencies.
Every source plugin requires its own, unique plugin ID to interoperate with Falco and the other plugins. The plugin ID will be used by the libs to properly process incoming events (for example, when saving events to file and loading them back), and by plugins to unuambiguosly recognize their dependencies.
To facilitate the allocation and distribution of plugin IDs, we will require that plugin developers request IDs for their plugins to the Falco organization. The mechanism used for plugin allocation is not determined yet and will be discussed in the future.
@@ -270,7 +270,7 @@ typedef struct
// Arguments:
// - s: the plugin state returned by init()
// - params: the open parameters, as a string. The format is defined by the plugin
// itself
// itsef
// - rc: pointer to an integer that will contain the open result, as a SCAP_* value
// (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1)
// Return value: a pointer to the open context that will be passed to next(),
@@ -474,7 +474,7 @@ typedef struct
// Return value: a json array of strings containing event
// sources returned by a source plugin's get_event_source()
// function.
// This function is optional--if NULL then the extractor
// This function is optional--if NULL then the exctractor
// plugin will receive every event.
//
char* (*get_extract_event_sources)();
@@ -526,7 +526,7 @@ We will also make a change to compile rules/macros/lists selectively based on th
### Handling Duplicate/Overlapping Fields in Plugins/Libraries Core
At an initial glance, adding plugins introduces the possibility of tens/hundreds of new filtercheck fields that could potentially overlap/conflict. For example, what happens if a plugin defines a "proc.name" field? However, the notion of "event source" makes these potential conflicts manageable.
At an initial glance, adding plugins introduces the possibility of tens/hundreds of new filtercheck fields that could potentially overlap/conflict. For example, what happens if a plugin defines a "proc.name" field? However, the notion of "event source" makes these potential conflicts managable.
Remember that field extraction is always done in the context of an event, and each event can be mapped back to an event source. So we only need to ensure that filtercheck fields are distinct for a given event source. For example, it's perfectly valid for an AWS Cloudtrail plugin to define a proc.name field, as the events generated by that plugin are wholly separate from syscall events. For syscall events, the AWS Cloudtrail plugin is not involved and the core libraries extract the process name for the tid performing a syscall. For AWS Cloudtrail events, the core libraries are not involved in field extraction and is performed by the AWS Cloudtrail plugin instead.

View File

@@ -1,5 +1,5 @@
#
# Copyright (C) 2022 The Falco Authors.
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,9 +22,7 @@
# anything semver-compatible.
- required_plugin_versions:
- name: cloudtrail
version: 0.2.3
- name: json
version: 0.2.2
version: 0.1.0
# Note that this rule is disabled by default. It's useful only to
# verify that the cloudtrail plugin is sending events properly. The
@@ -341,7 +339,7 @@
json.value[/requestParameters/PublicAccessBlockConfiguration/BlockPublicAcls]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/IgnorePublicAcls]=false)
output:
A public access block for a bucket has been deleted
A pulic access block for a bucket has been deleted
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,

View File

@@ -1,5 +1,5 @@
#
# Copyright (C) 2022 The Falco Authors.
# Copyright (C) 2020 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -63,14 +63,11 @@
condition: rename or remove
- macro: spawned_process
condition: evt.type in (execve, execveat) and evt.dir=<
condition: evt.type = execve and evt.dir=<
- macro: create_symlink
condition: evt.type in (symlink, symlinkat) and evt.dir=<
- macro: create_hardlink
condition: evt.type in (link, linkat) and evt.dir=<
- macro: chmod
condition: (evt.type in (chmod, fchmod, fchmodat) and evt.dir=<)
@@ -219,13 +216,13 @@
- list: deb_binaries
items: [dpkg, dpkg-preconfigu, dpkg-reconfigur, dpkg-divert, apt, apt-get, aptitude,
frontend, preinst, add-apt-reposit, apt-auto-remova, apt-key,
apt-listchanges, unattended-upgr, apt-add-reposit, apt-cache, apt.systemd.dai
apt-listchanges, unattended-upgr, apt-add-reposit, apt-config, apt-cache, apt.systemd.dai
]
# The truncated dpkg-preconfigu is intentional, process names are
# truncated at the falcosecurity-libs level.
- list: package_mgmt_binaries
items: [rpm_binaries, deb_binaries, update-alternat, gem, npm, pip, pip3, sane-utils.post, alternatives, chef-client, apk, snapd]
items: [rpm_binaries, deb_binaries, update-alternat, gem, pip, pip3, sane-utils.post, alternatives, chef-client, apk, snapd]
- macro: package_mgmt_procs
condition: proc.name in (package_mgmt_binaries)
@@ -443,9 +440,6 @@
- list: shell_config_directories
items: [/etc/zsh]
- macro: user_known_shell_config_modifiers
condition: (never_true)
- rule: Modify Shell Configuration File
desc: Detect attempt to modify shell configuration files
condition: >
@@ -455,7 +449,6 @@
fd.directory in (shell_config_directories))
and not proc.name in (shell_binaries)
and not exe_running_docker_save
and not user_known_shell_config_modifiers
output: >
a shell configuration file has been modified (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pcmdline=%proc.pcmdline file=%fd.name container_id=%container.id image=%container.image.repository)
priority:
@@ -568,7 +561,7 @@
# Qualys seems to run a variety of shell subprocesses, at various
# levels. This checks at a few levels without the cost of a full
# proc.aname, which traverses the full parent hierarchy.
# proc.aname, which traverses the full parent heirarchy.
- macro: run_by_qualys
condition: >
(proc.pname=qualys-cloud-ag or
@@ -779,9 +772,6 @@
- macro: centrify_writing_krb
condition: (proc.name in (adjoin,addns) and fd.name startswith /etc/krb5)
- macro: sssd_writing_krb
condition: (proc.name=adcli and proc.aname[2]=sssd and fd.name startswith /etc/krb5)
- macro: cockpit_writing_conf
condition: >
((proc.pname=cockpit-kube-la or proc.aname[2]=cockpit-kube-la)
@@ -1110,7 +1100,7 @@
condition: (proc.name=oc and fd.name startswith /etc/origin/node)
- macro: keepalived_writing_conf
condition: (proc.name in (keepalived, kube-keepalived) and fd.name=/etc/keepalived/keepalived.conf)
condition: (proc.name=keepalived and fd.name=/etc/keepalived/keepalived.conf)
- macro: etcd_manager_updating_dns
condition: (container and proc.name=etcd-manager and fd.name=/etc/hosts)
@@ -1225,7 +1215,6 @@
and not nginx_writing_certs
and not chef_client_writing_conf
and not centrify_writing_krb
and not sssd_writing_krb
and not cockpit_writing_conf
and not ipsec_writing_conf
and not httpd_writing_ssl_conf
@@ -1833,7 +1822,6 @@
gke.gcr.io/kube-proxy,
gke.gcr.io/gke-metadata-server,
gke.gcr.io/netd-amd64,
gke.gcr.io/watcher-daemonset,
gcr.io/google-containers/prometheus-to-sd,
k8s.gcr.io/ip-masq-agent-amd64,
k8s.gcr.io/kube-proxy,
@@ -1883,6 +1871,19 @@
container.image.repository in (falco_sensitive_mount_images) or
container.image.repository startswith quay.io/sysdig/)
# These container images are allowed to run with hostnetwork=true
- list: falco_hostnetwork_images
items: [
gcr.io/google-containers/prometheus-to-sd,
gcr.io/projectcalico-org/typha,
gcr.io/projectcalico-org/node,
gke.gcr.io/gke-metadata-server,
gke.gcr.io/kube-proxy,
gke.gcr.io/netd-amd64,
k8s.gcr.io/ip-masq-agent-amd64
k8s.gcr.io/prometheus-to-sd,
]
# Add conditions to this macro (probably in a separate file,
# overwriting this macro) to specify additional containers that are
# allowed to perform sensitive mounts.
@@ -2198,7 +2199,7 @@
# output: "sshd sent error message to syslog (error=%evt.buffer)"
# priority: WARNING
- macro: somebody_becoming_themselves
- macro: somebody_becoming_themself
condition: ((user.name=nobody and evt.arg.uid=nobody) or
(user.name=www-data and evt.arg.uid=www-data) or
(user.name=_apt and evt.arg.uid=_apt) or
@@ -2236,7 +2237,7 @@
evt.type=setuid and evt.dir=>
and (known_user_in_container or not container)
and not (user.name=root or user.uid=0)
and not somebody_becoming_themselves
and not somebody_becoming_themself
and not proc.name in (known_setuid_binaries, userexec_binaries, mail_binaries, docker_binaries,
nomachine_binaries)
and not proc.name startswith "runc:"
@@ -2260,7 +2261,7 @@
activity by any programs that can manage users, passwords, or permissions. sudo and su are excluded.
Activity in containers is also excluded--some containers create custom users on top
of a base linux distribution at startup.
Some innocuous command lines that don't actually change anything are excluded.
Some innocuous commandlines that don't actually change anything are excluded.
condition: >
spawned_process and proc.name in (user_mgmt_binaries) and
not proc.name in (su, sudo, lastlog, nologin, unix_chkpwd) and not container and
@@ -2310,7 +2311,7 @@
# explicitly enumerate the container images that you want to allow
# access to EC2 metadata. In this main falco rules file, there isn't
# any way to know all the containers that should have access, so any
# container is allowed, by repeating the "container" macro. In the
# container is alllowed, by repeating the "container" macro. In the
# overridden macro, the condition would look something like
# (container.image.repository = vendor/container-1 or
# container.image.repository = vendor/container-2 or ...)
@@ -2573,15 +2574,15 @@
condition: >
(modify and (
evt.arg.name contains "bash_history" or
evt.arg.name endswith "zsh_history" or
evt.arg.name contains "zsh_history" or
evt.arg.name contains "fish_read_history" or
evt.arg.name endswith "fish_history" or
evt.arg.oldpath contains "bash_history" or
evt.arg.oldpath endswith "zsh_history" or
evt.arg.oldpath contains "zsh_history" or
evt.arg.oldpath contains "fish_read_history" or
evt.arg.oldpath endswith "fish_history" or
evt.arg.path contains "bash_history" or
evt.arg.path endswith "zsh_history" or
evt.arg.path contains "zsh_history" or
evt.arg.path contains "fish_read_history" or
evt.arg.path endswith "fish_history"))
@@ -2589,7 +2590,7 @@
condition: >
(open_write and (
fd.name contains "bash_history" or
fd.name endswith "zsh_history" or
fd.name contains "zsh_history" or
fd.name contains "fish_read_history" or
fd.name endswith "fish_history") and evt.arg.flags contains "O_TRUNC")
@@ -2628,7 +2629,7 @@
items: [hyperkube, kubelet, k3s-agent]
# This macro should be overridden in user rules as needed. This is useful if a given application
# should not be ignored altogether with the user_known_chmod_applications list, but only in
# should not be ignored alltogether with the user_known_chmod_applications list, but only in
# specific conditions.
- macro: user_known_set_setuid_or_setgid_bit_conditions
condition: (never_true)
@@ -2708,17 +2709,7 @@
(evt.arg.target in (sensitive_file_names) or evt.arg.target in (sensitive_directory_names))
output: >
Symlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname)
priority: WARNING
tags: [file, mitre_exfiltration]
- rule: Create Hardlink Over Sensitive Files
desc: Detect hardlink created over sensitive files
condition: >
create_hardlink and
(evt.arg.oldpath in (sensitive_file_names))
output: >
Hardlinks created over sensitive files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.oldpath linkpath=%evt.arg.newpath parent_process=%proc.pname)
priority: WARNING
priority: NOTICE
tags: [file, mitre_exfiltration]
- list: miner_ports
@@ -2815,6 +2806,7 @@
- macro: trusted_images_query_miner_domain_dns
condition: (container.image.repository in (docker.io/falcosecurity/falco, falcosecurity/falco, public.ecr.aws/falcosecurity/falco))
append: false
# The rule is disabled by default.
# Note: falco will send DNS request to resolve miner pool domain which may trigger alerts in your environment.
@@ -2822,13 +2814,13 @@
desc: Miners typically connect to miner pools on common ports.
condition: net_miner_pool and not trusted_images_query_miner_domain_dns
enabled: false
output: Outbound connection to IP/Port flagged by https://cryptoioc.ch (command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository)
output: Outbound connection to IP/Port flagged by cryptoioc.ch (command=%proc.cmdline port=%fd.rport ip=%fd.rip container=%container.info image=%container.image.repository)
priority: CRITICAL
tags: [network, mitre_execution]
- rule: Detect crypto miners using the Stratum protocol
desc: Miners typically specify the mining pool to connect to with a URI that begins with 'stratum+tcp'
condition: spawned_process and (proc.cmdline contains "stratum+tcp" or proc.cmdline contains "stratum2+tcp" or proc.cmdline contains "stratum+ssl" or proc.cmdline contains "stratum2+ssl")
condition: spawned_process and proc.cmdline contains "stratum+tcp"
output: Possible miner running (command=%proc.cmdline container=%container.info image=%container.image.repository)
priority: CRITICAL
tags: [process, mitre_execution]
@@ -3047,7 +3039,7 @@
# A privilege escalation to root through heap-based buffer overflow
- rule: Sudo Potential Privilege Escalation
desc: Privilege escalation vulnerability affecting sudo (<= 1.9.5p2). Executing sudo using sudoedit -s or sudoedit -i command with command-line argument that ends with a single backslash character from an unprivileged user it's possible to elevate the user privileges to root.
condition: spawned_process and user.uid != 0 and (proc.name=sudoedit or proc.name = sudo) and (proc.args contains -s or proc.args contains -i or proc.args contains --login) and (proc.args contains "\ " or proc.args endswith \)
condition: spawned_process and user.uid != 0 and proc.name=sudoedit and (proc.args contains -s or proc.args contains -i) and (proc.args contains "\ " or proc.args endswith \)
output: "Detect Sudo Privilege Escalation Exploit (CVE-2021-3156) (user=%user.name parent=%proc.pname cmdline=%proc.cmdline %container.info)"
priority: CRITICAL
tags: [filesystem, mitre_privilege_escalation]
@@ -3065,17 +3057,13 @@
- macro: mount_info
condition: (proc.args="" or proc.args intersects ("-V", "-l", "-h"))
- macro: user_known_mount_in_privileged_containers
condition: (never_true)
- rule: Mount Launched in Privileged Container
desc: Detect file system mount happened inside a privileged container which might lead to container escape.
desc: Detect file system mount happened inside a privilegd container which might lead to container escape.
condition: >
spawned_process and container
and container.privileged=true
and proc.name=mount
and not mount_info
and not user_known_mount_in_privileged_containers
output: Mount was executed inside a privileged container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline %container.info image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [container, cis, mitre_lateral_movement]
@@ -3111,7 +3099,10 @@
- macro: curl_download
condition: proc.name = curl and
(proc.cmdline contains " -o " or
(proc.cmdline contains " > " or
proc.cmdline contains " >> " or
proc.cmdline contains " | " or
proc.cmdline contains " -o " or
proc.cmdline contains " --output " or
proc.cmdline contains " -O " or
proc.cmdline contains " --remote-name ")
@@ -3129,17 +3120,7 @@
priority: NOTICE
tags: [network, process, mitre_command_and_control]
# This rule helps detect CVE-2021-4034:
# A privilege escalation to root through memory corruption
- rule: Polkit Local Privilege Escalation Vulnerability (CVE-2021-4034)
desc: "This rule detects an attempt to exploit a privilege escalation vulnerability in Polkit's pkexec. By running specially crafted code, a local user can leverage this flaw to gain root privileges on a compromised system"
condition:
spawned_process and user.uid != 0 and proc.name=pkexec and proc.args = ''
output:
"Detect Polkit pkexec Local Privilege Escalation Exploit (CVE-2021-4034) (user=%user.loginname uid=%user.loginuid command=%proc.cmdline args=%proc.args)"
priority: CRITICAL
tags: [process, mitre_privilege_escalation]
# Application rules have moved to application_rules.yaml. Please look
# there if you want to enable them by adding to
# falco_rules.local.yaml.

View File

@@ -51,24 +51,13 @@
cluster-autoscaler,
"system:addon-manager",
"cloud-controller-manager",
"eks:node-manager",
"system:kube-controller-manager"
]
- list: eks_allowed_k8s_users
items: [
"eks:node-manager",
"eks:certificate-controller",
"eks:fargate-scheduler",
"eks:k8s-metrics",
"eks:authenticator",
"eks:cluster-event-watcher",
"eks:nodewatcher",
"eks:pod-identity-mutating-webhook"
]
-
- rule: Disallowed K8s User
desc: Detect any k8s operation by users outside of an allowed set of users.
condition: kevt and non_system_user and not ka.user.name in (allowed_k8s_users) and not ka.user.name in (eks_allowed_k8s_users)
condition: kevt and non_system_user and not ka.user.name in (allowed_k8s_users)
output: K8s Operation performed by user not in allowed list of users (user=%ka.user.name target=%ka.target.name/%ka.target.resource verb=%ka.verb uri=%ka.uri resp=%ka.response.code)
priority: WARNING
source: k8s_audit
@@ -163,19 +152,6 @@
source: k8s_audit
tags: [k8s]
# These container images are allowed to run with hostnetwork=true
- list: falco_hostnetwork_images
items: [
gcr.io/google-containers/prometheus-to-sd,
gcr.io/projectcalico-org/typha,
gcr.io/projectcalico-org/node,
gke.gcr.io/gke-metadata-server,
gke.gcr.io/kube-proxy,
gke.gcr.io/netd-amd64,
k8s.gcr.io/ip-masq-agent-amd64
k8s.gcr.io/prometheus-to-sd,
]
# Corresponds to K8s CIS Benchmark 1.7.4
- rule: Create HostNetwork Pod
desc: Detect an attempt to start a pod using the host network.
@@ -185,28 +161,6 @@
source: k8s_audit
tags: [k8s]
- list: falco_hostpid_images
items: []
- rule: Create HostPid Pod
desc: Detect an attempt to start a pod using the host pid namespace.
condition: kevt and pod and kcreate and ka.req.pod.host_pid intersects (true) and not ka.req.pod.containers.image.repository in (falco_hostpid_images)
output: Pod started using host pid namespace (user=%ka.user.name pod=%ka.resp.name ns=%ka.target.namespace images=%ka.req.pod.containers.image)
priority: WARNING
source: k8s_audit
tags: [k8s]
- list: falco_hostipc_images
items: []
- rule: Create HostIPC Pod
desc: Detect an attempt to start a pod using the host ipc namespace.
condition: kevt and pod and kcreate and ka.req.pod.host_ipc intersects (true) and not ka.req.pod.containers.image.repository in (falco_hostipc_images)
output: Pod started using host ipc namespace (user=%ka.user.name pod=%ka.resp.name ns=%ka.target.namespace images=%ka.req.pod.containers.image)
priority: WARNING
source: k8s_audit
tags: [k8s]
- macro: user_known_node_port_service
condition: (k8s_audit_never_true)
@@ -321,7 +275,6 @@
k8s.gcr.io/kube-apiserver,
gke.gcr.io/kube-proxy,
gke.gcr.io/netd-amd64,
gke.gcr.io/watcher-daemonset,
k8s.gcr.io/addon-resizer
k8s.gcr.io/prometheus-to-sd,
k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64,
@@ -400,7 +353,7 @@
tags: [k8s]
# Detect any attempt to create a ClusterRoleBinding to the cluster-admin user
# (expand this to any built-in cluster role that does "sensitive" things)
# (exapand this to any built-in cluster role that does "sensitive" things)
- rule: Attach to cluster-admin Role
desc: Detect any attempt to create a ClusterRoleBinding to the cluster-admin user
condition: kevt and clusterrolebinding and kcreate and ka.req.binding.role=cluster-admin
@@ -513,7 +466,7 @@
- rule: K8s Serviceaccount Created
desc: Detect any attempt to create a service account
condition: (kactivity and kcreate and serviceaccount and response_successful)
output: K8s Serviceaccount Created (user=%ka.user.name serviceaccount=%ka.target.name ns=%ka.target.namespace resp=%ka.response.code decision=%ka.auth.decision reason=%ka.auth.reason)
output: K8s Serviceaccount Created (user=%ka.user.name user=%ka.target.name ns=%ka.target.namespace resp=%ka.response.code decision=%ka.auth.decision reason=%ka.auth.reason)
priority: INFO
source: k8s_audit
tags: [k8s]
@@ -521,7 +474,7 @@
- rule: K8s Serviceaccount Deleted
desc: Detect any attempt to delete a service account
condition: (kactivity and kdelete and serviceaccount and response_successful)
output: K8s Serviceaccount Deleted (user=%ka.user.name serviceaccount=%ka.target.name ns=%ka.target.namespace resp=%ka.response.code decision=%ka.auth.decision reason=%ka.auth.reason)
output: K8s Serviceaccount Deleted (user=%ka.user.name user=%ka.target.name ns=%ka.target.namespace resp=%ka.response.code decision=%ka.auth.decision reason=%ka.auth.reason)
priority: INFO
source: k8s_audit
tags: [k8s]
@@ -606,7 +559,7 @@
# 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
# may or may not be an administrator. Customize the full_admin_k8s_users
# list to your needs, and activate at your discretion.
# list to your needs, and activate at your discrection.
# # How to test:
# # Execute any kubectl command connected using default cluster user, as:

View File

@@ -1,177 +0,0 @@
#Example Rule on login in to OKTA. Disabled by default since it might be noisy
#- rule: User logged in to OKTA
# desc: Detect the user login in to OKTA
# condition: okta.evt.type = "user.session.start"
# output: "A user has logged in toOKTA (user=%okta.actor.name, ip=%okta.client.ip)"
# priority: NOTICE
# source: okta
# tags: [okta]
- rule: User Changing password in to OKTA
desc: Detect a user change password in OKTA
condition: okta.evt.type = "user.account.update_password"
output: "A user has changed password from OKTA (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: Creating a new OKTA user account
desc: Detect a new OKTA user account created in the OKTA environment
condition: okta.evt.type = "user.lifecycle.create"
output: "A new OKTA user account created (user=%okta.actor.name, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: User accessing app via single sign on OKTA
desc: Detect a user accessing an app via OKTA
condition: okta.evt.type = "user.authentication.sso"
output: "A user has accessed an app using OKTA (user=%okta.actor.name, app=%okta.app)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: User has been locked out in OKTA
desc: Detect a user who has been locked out in OKTA
condition: okta.evt.type = "user.account.lock"
output: "A user has been locked out in OKTA (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
- rule: User has been moved from suspended status in OKTA.
desc: Detect a user who has been moved from suspended status in OKTA
condition: okta.evt.type = "user.lifecycle.unsuspend"
output: "A user has been moved from suspended status in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: User has been activated in OKTA
desc: Detect a user who has been activated in OKTA
condition: okta.evt.type = "user.lifecycle.activate"
output: "A user has been activated in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: User has been deactivated in OKTA
desc: Detect a user who has been deactivated in OKTA
condition: okta.evt.type = "user.lifecycle.deactivate"
output: "A user has been deactivated in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: User has been suspended in OKTA
desc: Detect a user who has been suspended in OKTA
condition: okta.evt.type = "user.lifecycle.suspended"
output: "A user has been suspended in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
- rule: Admin permission has been assigned to a user in OKTA
desc: Detect an admin permission assigned to a user in OKTA
condition: okta.evt.type = "user.account.privilege.grant"
output: "A user has been locked out in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
- rule: Creating a new OKTA API token
desc: Detect a new OKTA API token created in the OKTA environment
condition: okta.evt.type = "system.api_token.create"
output: "A new OKTA API token has been created in OKTA (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
- rule: User accessing OKTA admin section
desc: Detect a user accessing OKTA admin section of your OKTA instance
condition: okta.evt.type = "user.session.access_admin_app"
output: "A user accessed the OKTA admin section of your OKTA instance (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
- rule: Adding user in OKTA group
desc: Detect a new user added to an OKTA group
condition: okta.evt.type = "group.user_membership.add"
output: "A user has been added in an OKTA group (user=%okta.actor.name, target group=%okta.target.group.name, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: removing MFA factor from user in OKTA
desc: Detect a removing MFA activity on a user in OKTA
condition: okta.evt.type = "user.mfa.factor.deactivate"
output: "A user has removed MFA factor in the OKTA account (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
- rule: removing all MFA factor from user in OKTA
desc: Detect a removing MFA activity on a user in OKTA
condition: okta.evt.type = "user.mfa.factor.reset_all"
output: "A user has removed all MFA factor in the OKTA account (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
- rule: User password reset by OKTA admin
desc: Detect a password reset on a user done by OKTA Admin Account
condition: okta.evt.type = "user.account.reset_password"
output: "A user password has been reset by an OKTA Admin account (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
- rule: User hitting the rate limit on requests in OKTA
desc: Detect a user who hit the rate limit on requests in OKTA
condition: okta.evt.type = "system.org.rate_limit.violation"
output: "A user has hitted the rate limit on requests in OKTA (user=%okta.actor.name, ip=%okta.client.ip)"
priority: NOTICE
source: okta
tags: [okta]
- rule: Adding user to application membership in OKTA
desc: Detect a user who has been added o application membership in OKTA
condition: okta.evt.type = "application.user_membership.add"
output: "A user has been added to an application membership in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name, app=%okta.app)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false
- rule: User initiating impersonation session in OKTA
desc: Detect a user who initiate an impersonation session in OKTA
condition: okta.evt.type = "user.session.impersonation.initiate"
output: "A user has initiated an impersonation session in OKTA (user=%okta.actor.name, ip=%okta.client.ip, target user=%okta.target.user.name)"
priority: NOTICE
source: okta
tags: [okta]
# This list allows easily whitelisting countries that are
# expected to see OKTA logins from.
- list: allowed_countries_list
items: []
- macro: user_known_countries
condition: (okta.client.geo.country in (allowed_countries_list))
- rule: Detecting unknown logins using geolocation
desc: Detect a logins event based on user geolocation
condition: okta.evt.type = "user.session.start" and not user_known_countries
output: "A user logged in OKTA from a suspicious country (user=%okta.actor.name, ip=%okta.client.ip, country=%okta.client.geo.country)"
priority: NOTICE
source: okta
tags: [okta]
enabled: false

45
scripts/build-lpeg.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
#
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -ex
PREFIX=$1
if [ -z "$PREFIX" ]; then
PREFIX=.
fi
mkdir -p $PREFIX
gcc -O2 -fPIC -I"$LUA_INCLUDE" -c lpcap.c -o $PREFIX/lpcap.o
gcc -O2 -fPIC -I"$LUA_INCLUDE" -c lpcode.c -o $PREFIX/lpcode.o
gcc -O2 -fPIC -I"$LUA_INCLUDE" -c lpprint.c -o $PREFIX/lpprint.o
gcc -O2 -fPIC -I"$LUA_INCLUDE" -c lptree.c -o $PREFIX/lptree.o
gcc -O2 -fPIC -I"$LUA_INCLUDE" -c lpvm.c -o $PREFIX/lpvm.o
# For building lpeg.so, which we don't need now that we're statically linking lpeg.a into falco
#gcc -shared -o lpeg.so -L/usr/local/lib lpcap.o lpcode.o lpprint.o lptree.o lpvm.o
#gcc -shared -o lpeg.so -L/usr/local/lib lpcap.o lpcode.o lpprint.o lptree.o lpvm.o
pushd $PREFIX
/usr/bin/ar cr lpeg.a lpcap.o lpcode.o lpprint.o lptree.o lpvm.o
/usr/bin/ranlib lpeg.a
popd
chmod ug+w re.lua

View File

@@ -18,7 +18,7 @@
set -e
DKMS_PACKAGE_NAME="@PACKAGE_NAME@"
DKMS_VERSION="@DRIVER_VERSION@"
DKMS_VERSION="@PROBE_VERSION@"
NAME="@PACKAGE_NAME@"
postinst_found=0

View File

@@ -18,7 +18,7 @@
set -e
DKMS_PACKAGE_NAME="@PACKAGE_NAME@"
DKMS_VERSION="@DRIVER_VERSION@"
DKMS_VERSION="@PROBE_VERSION@"
case "$1" in
remove|upgrade|deconfigure)

View File

@@ -160,26 +160,15 @@ load_kernel_module_compile() {
echo "make CC=${CURRENT_GCC} \$@" >> /tmp/falco-dkms-make
chmod +x /tmp/falco-dkms-make
if dkms install --directive="MAKE='/tmp/falco-dkms-make'" -m "${DRIVER_NAME}" -v "${DRIVER_VERSION}" -k "${KERNEL_RELEASE}" 2>/dev/null; then
echo "* ${DRIVER_NAME} module installed in dkms"
KO_FILE="/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}"
if [ -f "$KO_FILE.ko" ]; then
KO_FILE="$KO_FILE.ko"
elif [ -f "$KO_FILE.ko.gz" ]; then
KO_FILE="$KO_FILE.ko.gz"
elif [ -f "$KO_FILE.ko.xz" ]; then
KO_FILE="$KO_FILE.ko.xz"
elif [ -f "$KO_FILE.ko.zst" ]; then
KO_FILE="$KO_FILE.ko.zst"
else
>&2 echo "${DRIVER_NAME} module file not found"
return
fi
echo "* ${DRIVER_NAME} module found: ${KO_FILE}"
echo "* Trying insmod"
chcon -t modules_object_t "$KO_FILE" > /dev/null 2>&1 || true
if insmod "$KO_FILE" > /dev/null 2>&1; then
echo "* ${DRIVER_NAME} module installed in dkms, trying to insmod"
chcon -t modules_object_t "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko" > /dev/null 2>&1 || true
chcon -t modules_object_t "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko.xz" > /dev/null 2>&1 || true
if insmod "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko" > /dev/null 2>&1; then
echo "* Success: ${DRIVER_NAME} module found and loaded in dkms"
exit 0
elif insmod "/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}.ko.xz" > /dev/null 2>&1; then
echo "* Success: ${DRIVER_NAME} module found and loaded in dkms (xz)"
exit 0
else
echo "* Unable to insmod ${DRIVER_NAME} module"
fi
@@ -207,12 +196,8 @@ load_kernel_module_download() {
if curl -L --create-dirs "${FALCO_DRIVER_CURL_OPTIONS}" -o "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" "${URL}"; then
echo "* Download succeeded"
chcon -t modules_object_t "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true
if insmod "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}"; then
echo "* Success: ${DRIVER_NAME} module found and inserted"
exit 0
else
>&2 echo "Unable to insmod the prebuilt ${DRIVER_NAME} module"
fi
insmod "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" && echo "* Success: ${DRIVER_NAME} module found and inserted"
exit $?
else
>&2 echo "Unable to find a prebuilt ${DRIVER_NAME} module"
return
@@ -256,6 +241,11 @@ load_kernel_module() {
exit 0
fi
echo "* Trying to load a system ${DRIVER_NAME} module, if present"
if modprobe "${DRIVER_NAME}" > /dev/null 2>&1; then
echo "* Success: ${DRIVER_NAME} module found and loaded with modprobe"
exit 0
fi
echo "* Looking for a ${DRIVER_NAME} module locally (kernel ${KERNEL_RELEASE})"
@@ -278,13 +268,6 @@ load_kernel_module() {
load_kernel_module_compile
fi
# Last try (might load a previous driver version)
echo "* Trying to load a system ${DRIVER_NAME} module, if present"
if modprobe "${DRIVER_NAME}" > /dev/null 2>&1; then
echo "* Success: ${DRIVER_NAME} module found and loaded with modprobe"
exit 0
fi
# Not able to download a prebuilt module nor to compile one on-the-fly
>&2 echo "Consider compiling your own ${DRIVER_NAME} driver and loading it or getting in touch with the Falco community"
exit 1
@@ -514,7 +497,7 @@ print_usage() {
echo " --source-only skip execution and allow sourcing in another script"
echo ""
echo "Environment variables:"
echo " DRIVERS_REPO specify a different URL where to look for prebuilt Falco drivers"
echo " DRIVER_REPO specify a different URL where to look for prebuilt Falco drivers"
echo " DRIVER_NAME specify a different name for the driver"
echo " DRIVER_INSECURE_DOWNLOAD whether you want to allow insecure downloads or not"
echo ""
@@ -547,8 +530,8 @@ if [[ -z "$MAX_RMMOD_WAIT" ]]; then
MAX_RMMOD_WAIT=60
fi
DRIVER_VERSION="@DRIVER_VERSION@"
DRIVER_NAME=${DRIVER_NAME:-"@DRIVER_NAME@"}
DRIVER_VERSION="@PROBE_VERSION@"
DRIVER_NAME=${DRIVER_NAME:-"@PROBE_NAME@"}
FALCO_VERSION="@FALCO_VERSION@"
DRIVER="module"

View File

@@ -15,7 +15,7 @@
# limitations under the License.
#
mod_version="@DRIVER_VERSION@"
mod_version="@PROBE_VERSION@"
dkms add -m falco -v $mod_version --rpm_safe_upgrade
if [ `uname -r | grep -c "BOOT"` -eq 0 ] && [ -e /lib/modules/`uname -r`/build/include ]; then
dkms build -m falco -v $mod_version

View File

@@ -15,5 +15,5 @@
# limitations under the License.
#
mod_version="@DRIVER_VERSION@"
mod_version="@PROBE_VERSION@"
dkms remove -m falco -v $mod_version --all --rpm_safe_upgrade

3
test/.gitignore vendored
View File

@@ -1,2 +1 @@
falco_traces.yaml
venv/*
falco_traces.yaml

View File

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

View File

@@ -71,7 +71,7 @@ The `falco_traces.yaml` test suite gets generated through the `falco_traces.yaml
### falco_tests_package
The `falco_tests_package.yaml` test suite requires some additional setup steps to be successfully run on your local machine.
The `falco_tests_package.yaml` test suite requires some additional setup steps to be succesfully run on your local machine.
In particular, it requires some runners (ie., docker images) to be already built and present into your local machine.

View File

@@ -49,7 +49,6 @@ trace_files: !mux
detect: False
rules_file:
- ../rules/falco_rules.yaml
- ../rules/k8s_audit_rules.yaml
- ./rules/k8s_audit/engine_v4_k8s_audit_rules.yaml
- ./rules/k8s_audit/trust_nginx_container.yaml
trace_file: trace_files/k8s_audit/create_nginx_pod_privileged.json
@@ -75,7 +74,6 @@ trace_files: !mux
detect: False
rules_file:
- ../rules/falco_rules.yaml
- ../rules/k8s_audit_rules.yaml
- ./rules/k8s_audit/engine_v4_k8s_audit_rules.yaml
- ./rules/k8s_audit/trust_nginx_container.yaml
trace_file: trace_files/k8s_audit/create_nginx_pod_hostnetwork.json

View File

@@ -524,10 +524,10 @@ class FalcoTest(Test):
["md5sum", "/boot/config-{}".format(kernel_release)]).rstrip()
config_hash = md5_output.split(" ")[0]
driver_filename = "falco-{}-{}-{}-{}.ko".format(
probe_filename = "falco-{}-{}-{}-{}.ko".format(
falco_version, arch, kernel_release, config_hash)
driver_path = os.path.join(self.falcodir, "driver", "falco.ko")
module_path = os.path.join(self.module_dir, driver_filename)
module_path = os.path.join(self.module_dir, probe_filename)
self.log.debug("Copying {} to {}".format(driver_path, module_path))
shutil.copyfile(driver_path, module_path)

View File

@@ -44,8 +44,8 @@ trace_files: !mux
- not_equals_after_evttype: [execve]
- not_after_evttype: [execve]
- leading_trailing_evttypes: [execve,open]
- leading_multitrailing_evttypes: [connect,execve,open]
- leading_multitrailing_evttypes_using_in: [connect,execve,open]
- leading_multtrailing_evttypes: [connect,execve,open]
- leading_multtrailing_evttypes_using_in: [connect,execve,open]
- not_equals_at_end: [all]
- not_at_end: [all]
- not_before_trailing_evttype: [all]
@@ -254,6 +254,9 @@ trace_files: !mux
stdout_is: |+
1 errors:
Rules content is not yaml
---
This is not yaml
---
validate_rules_file:
- rules/invalid_not_yaml.yaml
trace_file: trace_files/cat_write.scap
@@ -263,6 +266,9 @@ trace_files: !mux
stdout_is: |+
1 errors:
Rules content is not yaml array of objects
---
foo: bar
---
validate_rules_file:
- rules/invalid_not_array.yaml
trace_file: trace_files/cat_write.scap
@@ -271,7 +277,7 @@ trace_files: !mux
exit_status: 1
stdout_is: |+
1 errors:
Unexpected element type. Each element should be a yaml associative array.
Unexpected element of type string. Each element should be a yaml associative array.
---
- foo
---
@@ -293,6 +299,12 @@ trace_files: !mux
invalid_yaml_parse_error:
exit_status: 1
stdout_is: |+
1 errors:
mapping values are not allowed in this context
---
this : is : not : yaml
---
validate_rules_file:
- rules/invalid_yaml_parse_error.yaml
trace_file: trace_files/cat_write.scap
@@ -327,7 +339,7 @@ trace_files: !mux
exit_status: 1
stdout_is: |+
1 errors:
Rule must have properties 'condition', 'output', 'desc', and 'priority'
Rule must have property output
---
- rule: no output rule
desc: some desc
@@ -414,7 +426,7 @@ trace_files: !mux
stdout_contains: |+
.*invalid_base_macro.yaml: Ok
.*invalid_append_macro.yaml: 1 errors:
Compilation error when compiling "evt.type=execve foo": 17: unexpected token after 'execve', expecting 'or', 'and'
Compilation error when compiling "evt.type=execve foo": 17: syntax error, unexpected 'foo', expecting 'or', 'and'
---
- macro: some macro
condition: evt.type=execve
@@ -446,7 +458,7 @@ trace_files: !mux
exit_status: 1
stdout_is: |+
1 errors:
Compilation error when compiling "evt.type=execve foo": 17: unexpected token after 'execve', expecting 'or', 'and'
Compilation error when compiling "evt.type=execve foo": 17: syntax error, unexpected 'foo', expecting 'or', 'and'
---
- macro: some macro
condition: evt.type=execve
@@ -483,7 +495,7 @@ trace_files: !mux
stdout_contains: |+
.*invalid_base_rule.yaml: Ok
.*invalid_append_rule.yaml: 1 errors:
Compilation error when compiling "evt.type=open bar": 15: unexpected token after 'open', expecting 'or', 'and'
Compilation error when compiling "evt.type=open bar": 15: syntax error, unexpected 'bar', expecting 'or', 'and'
---
- rule: some rule
desc: some desc
@@ -523,7 +535,7 @@ trace_files: !mux
invalid_append_rule_multiple_docs:
exit_status: 1
stdout_contains: |+
Compilation error when compiling "evt.type=open bar": 15: unexpected token after 'open', expecting 'or', 'and'
Compilation error when compiling "evt.type=open bar": 15: syntax error, unexpected 'bar', expecting 'or', 'and'
---
- rule: some rule
desc: some desc

View File

@@ -82,7 +82,7 @@ trace_files: !mux
incompat_plugin_rules_version:
exit_status: 1
stderr_contains: "Runtime error: Plugin cloudtrail version .* not compatible with required plugin version 100000.0.0. Exiting."
stderr_contains: "Runtime error: Plugin cloudtrail version 0.1.0 not compatible with required plugin version 100000.0.0. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
rules_file:
- rules/plugins/cloudtrail_incompat_plugin_version.yaml
@@ -103,20 +103,4 @@ trace_files: !mux
- Cloudtrail Create Instance
stderr_contains: "Rule Cloudtrail Create Instance: warning .unknown-source.: unknown source aws_cloudtrail, skipping"
no_plugins_unknown_source_macro:
detect: False
rules_file:
- rules/plugins/cloudtrail_macro.yaml
trace_file: trace_files/empty.scap
stderr_contains: "Macro Some Cloudtrail Macro: warning .unknown-source.: unknown source aws_cloudtrail, skipping"
no_plugins_unknown_source_rule_exception:
detect: False
rules_file:
- rules/plugins/cloudtrail_create_instances_exceptions.yaml
trace_file: trace_files/empty.scap
rules_warning:
- Cloudtrail Create Instance
stderr_contains: "Rule Cloudtrail Create Instance: warning .unknown-source.: unknown source aws_cloudtrail, skipping"

View File

@@ -19,7 +19,7 @@ limitations under the License.
#include <string.h>
#include <plugin_info.h>
static const char *pl_required_api_version = "1.0.0";
static const char *pl_required_api_version = "0.1.0";
static uint32_t pl_type = TYPE_EXTRACTOR_PLUGIN;
static const char *pl_name_base = "test_extract";
static char pl_name[1024];

View File

@@ -20,7 +20,7 @@ limitations under the License.
#include <plugin_info.h>
static const char *pl_required_api_version = "1.0.0";
static const char *pl_required_api_version = "0.1.0";
static uint32_t pl_type = TYPE_SOURCE_PLUGIN;
static uint32_t pl_id = 999;
static const char *pl_name = "test_source";

View File

@@ -56,13 +56,13 @@
output: "None"
priority: WARNING
- rule: leading_multitrailing_evttypes
- rule: leading_multtrailing_evttypes
desc: one evttype at beginning, multiple at end
condition: evt.type=execve and proc.name=foo or evt.type=open or evt.type=connect
output: "None"
priority: WARNING
- rule: leading_multitrailing_evttypes_using_in
- rule: leading_multtrailing_evttypes_using_in
desc: one evttype at beginning, multiple at end, using in
condition: evt.type=execve and proc.name=foo or evt.type in (open, connect)
output: "None"

View File

@@ -246,7 +246,7 @@
tags: [k8s]
# Detect any attempt to create a ClusterRoleBinding to the cluster-admin user
# (expand this to any built-in cluster role that does "sensitive" things)
# (exapand this to any built-in cluster role that does "sensitive" things)
- rule: Attach to cluster-admin Role
desc: Detect any attempt to create a ClusterRoleBinding to the cluster-admin user
condition: kevt and clusterrolebinding and kcreate and ka.req.binding.role=cluster-admin

View File

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

View File

@@ -1,4 +0,0 @@
- macro: Some Cloudtrail Macro
condition: aws.user=bob
source: aws_cloudtrail

View File

@@ -2,11 +2,19 @@ add_subdirectory(k8s_audit)
add_subdirectory(psp)
add_subdirectory(plugins)
include(copy_files_to_build_dir)
# Note: list of traces is created at cmake time, not build time
file(GLOB test_trace_files
"${CMAKE_CURRENT_SOURCE_DIR}/*.scap")
copy_files_to_build_dir("${test_trace_files}" base-scap)
foreach(trace_file_path ${test_trace_files})
get_filename_component(trace_file ${trace_file_path} NAME)
add_custom_target(test-trace-${trace_file} ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${trace_file})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${trace_file}
COMMAND ${CMAKE_COMMAND} -E copy ${trace_file_path} ${CMAKE_CURRENT_BINARY_DIR}/${trace_file}
DEPENDS ${trace_file_path})
list(APPEND BASE_SCAP_TRACE_FILES_TARGETS test-trace-${trace_file})
endforeach()
add_custom_target(trace-files-base-scap ALL)
add_dependencies(trace-files-base-scap ${BASE_SCAP_TRACE_FILES_TARGETS})

View File

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

View File

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

View File

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

View File

@@ -20,8 +20,6 @@ if(MINIMAL_BUILD)
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
engine/test_filter_macro_resolver.cpp
engine/test_filter_evttype_resolver.cpp
falco/test_configuration.cpp
)
else()
@@ -30,8 +28,6 @@ else()
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
engine/test_filter_macro_resolver.cpp
engine/test_filter_evttype_resolver.cpp
falco/test_configuration.cpp
falco/test_webserver.cpp
)

View File

@@ -1,237 +0,0 @@
/*
Copyright (C) 2021 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "filter_evttype_resolver.h"
#include <catch.hpp>
#include <sinsp.h>
#include <filter/parser.h>
using namespace std;
using namespace libsinsp::filter;
string to_string(set<uint16_t> s)
{
string out = "[";
for(auto &val : s)
{
out += out.size() == 1 ? "" : ", ";
out += to_string(val);
}
out += "]";
return out;
}
void compare_evttypes(ast::expr* f, set<uint16_t> &expected)
{
set<uint16_t> actual;
filter_evttype_resolver resolver;
resolver.evttypes(f, actual);
for(auto &etype : expected)
{
REQUIRE(actual.find(etype) != actual.end());
}
for(auto &etype : actual)
{
REQUIRE(expected.find(etype) != expected.end());
}
}
ast::expr* compile(const string &fltstr)
{
libsinsp::filter::parser p(fltstr);
return p.parse();
}
TEST_CASE("Should find event types from filter", "[rule_loader]")
{
filter_evttype_resolver resolver;
set<uint16_t> openat_only{
PPME_SYSCALL_OPENAT_2_E, PPME_SYSCALL_OPENAT_2_X };
set<uint16_t> close_only{
PPME_SYSCALL_CLOSE_E, PPME_SYSCALL_CLOSE_X };
set<uint16_t> openat_close{
PPME_SYSCALL_OPENAT_2_E, PPME_SYSCALL_OPENAT_2_X,
PPME_SYSCALL_CLOSE_E, PPME_SYSCALL_CLOSE_X };
set<uint16_t> not_openat;
set<uint16_t> not_openat_close;
set<uint16_t> not_close;
set<uint16_t> all_events;
set<uint16_t> no_events;
for(uint32_t i = 2; i < PPM_EVENT_MAX; i++)
{
// Skip "old" event versions that have been replaced
// by newer event versions, or events that are unused.
if(g_infotables.m_event_info[i].flags & (EF_OLD_VERSION | EF_UNUSED))
{
continue;
}
all_events.insert(i);
if(openat_only.find(i) == openat_only.end())
{
not_openat.insert(i);
}
if(openat_close.find(i) == openat_close.end())
{
not_openat_close.insert(i);
}
if (close_only.find(i) == close_only.end())
{
not_close.insert(i);
}
}
SECTION("evt_type_eq")
{
auto f = compile("evt.type=openat");
compare_evttypes(f, openat_only);
}
SECTION("evt_type_in")
{
auto f = compile("evt.type in (openat, close)");
compare_evttypes(f, openat_close);
}
SECTION("evt_type_ne")
{
auto f = compile("evt.type!=openat");
compare_evttypes(f, not_openat);
}
SECTION("not_evt_type_eq")
{
auto f = compile("not evt.type=openat");
compare_evttypes(f, not_openat);
}
SECTION("not_evt_type_in")
{
auto f = compile("not evt.type in (openat, close)");
compare_evttypes(f, not_openat_close);
}
SECTION("not_evt_type_ne")
{
auto f = compile("not evt.type != openat");
compare_evttypes(f, openat_only);
}
SECTION("evt_type_or")
{
auto f = compile("evt.type=openat or evt.type=close");
compare_evttypes(f, openat_close);
}
SECTION("not_evt_type_or")
{
auto f = compile("evt.type!=openat or evt.type!=close");
compare_evttypes(f, all_events);
}
SECTION("evt_type_or_ne")
{
auto f = compile("evt.type=close or evt.type!=openat");
compare_evttypes(f, not_openat);
}
SECTION("evt_type_and")
{
auto f = compile("evt.type=close and evt.type=openat");
compare_evttypes(f, no_events);
}
SECTION("evt_type_and_non_evt_type")
{
auto f = compile("evt.type=openat and proc.name=nginx");
compare_evttypes(f, openat_only);
}
SECTION("evt_type_and_non_evt_type_not")
{
auto f = compile("evt.type=openat and not proc.name=nginx");
compare_evttypes(f, openat_only);
}
SECTION("evt_type_and_nested")
{
auto f = compile("evt.type=openat and (proc.name=nginx)");
compare_evttypes(f, openat_only);
}
SECTION("evt_type_and_nested_multi")
{
auto f = compile("evt.type=openat and (evt.type=close and proc.name=nginx)");
compare_evttypes(f, no_events);
}
SECTION("non_evt_type")
{
auto f = compile("proc.name=nginx");
compare_evttypes(f, all_events);
}
SECTION("non_evt_type_or")
{
auto f = compile("evt.type=openat or proc.name=nginx");
compare_evttypes(f, all_events);
}
SECTION("non_evt_type_or_nested_first")
{
auto f = compile("(evt.type=openat) or proc.name=nginx");
compare_evttypes(f, all_events);
}
SECTION("non_evt_type_or_nested_second")
{
auto f = compile("evt.type=openat or (proc.name=nginx)");
compare_evttypes(f, all_events);
}
SECTION("non_evt_type_or_nested_multi")
{
auto f = compile("evt.type=openat or (evt.type=close and proc.name=nginx)");
compare_evttypes(f, openat_close);
}
SECTION("non_evt_type_or_nested_multi_not")
{
auto f = compile("evt.type=openat or not (evt.type=close and proc.name=nginx)");
compare_evttypes(f, not_close);
}
SECTION("non_evt_type_and_nested_multi_not")
{
auto f = compile("evt.type=openat and not (evt.type=close and proc.name=nginx)");
compare_evttypes(f, openat_only);
}
SECTION("ne_and_and")
{
auto f = compile("evt.type!=openat and evt.type!=close");
compare_evttypes(f, not_openat_close);
}
SECTION("not_not")
{
auto f = compile("not (not evt.type=openat)");
compare_evttypes(f, openat_only);
}
}

View File

@@ -1,275 +0,0 @@
/*
Copyright (C) 2020 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "filter_macro_resolver.h"
#include <catch.hpp>
using namespace std;
using namespace libsinsp::filter::ast;
TEST_CASE("Should resolve macros on a filter AST", "[rule_loader]")
{
string macro_name = "test_macro";
SECTION("in the general case")
{
shared_ptr<expr> macro(
new unary_check_expr("test.field", "", "exists"));
expr* filter = new and_expr({
new unary_check_expr("evt.name", "", "exists"),
new not_expr(
new value_expr(macro_name)
),
});
expr* expected_filter = new and_expr({
new unary_check_expr("evt.name", "", "exists"),
new not_expr(clone(macro.get())),
});
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter));
// second run
REQUIRE(resolver.run(filter) == false);
REQUIRE(resolver.get_resolved_macros().empty());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter));
delete filter;
delete expected_filter;
}
SECTION("with a single node")
{
shared_ptr<expr> macro(
new unary_check_expr("test.field", "", "exists"));
expr* filter = new value_expr(macro_name);
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
// first run
expr* old_filter_ptr = filter;
REQUIRE(resolver.run(filter) == true);
REQUIRE(filter != old_filter_ptr);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(macro.get()));
// second run
old_filter_ptr = filter;
REQUIRE(resolver.run(filter) == false);
REQUIRE(filter == old_filter_ptr);
REQUIRE(resolver.get_resolved_macros().empty());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(macro.get()));
delete filter;
}
SECTION("with multiple macros")
{
string a_macro_name = macro_name + "_1";
string b_macro_name = macro_name + "_2";
shared_ptr<expr> a_macro(
new unary_check_expr("one.field", "", "exists"));
shared_ptr<expr> b_macro(
new unary_check_expr("another.field", "", "exists"));
expr* filter = new or_expr({
new value_expr(a_macro_name),
new value_expr(b_macro_name),
});
expr* expected_filter = new or_expr({
clone(a_macro.get()),
clone(b_macro.get()),
});
filter_macro_resolver resolver;
resolver.set_macro(a_macro_name, a_macro);
resolver.set_macro(b_macro_name, b_macro);
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 2);
REQUIRE(resolver.get_resolved_macros().find(a_macro_name)
!= resolver.get_resolved_macros().end());
REQUIRE(resolver.get_resolved_macros().find(b_macro_name)
!= resolver.get_resolved_macros().end());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter));
// second run
REQUIRE(resolver.run(filter) == false);
REQUIRE(resolver.get_resolved_macros().empty());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter));
delete filter;
delete expected_filter;
}
SECTION("with nested macros")
{
string a_macro_name = macro_name + "_1";
string b_macro_name = macro_name + "_2";
shared_ptr<expr> a_macro(new and_expr({
new unary_check_expr("one.field", "", "exists"),
new value_expr(b_macro_name),
}));
shared_ptr<expr> b_macro(
new unary_check_expr("another.field", "", "exists"));
expr* filter = new value_expr(a_macro_name);
expr* expected_filter = new and_expr({
new unary_check_expr("one.field", "", "exists"),
new unary_check_expr("another.field", "", "exists"),
});
filter_macro_resolver resolver;
resolver.set_macro(a_macro_name, a_macro);
resolver.set_macro(b_macro_name, b_macro);
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 2);
REQUIRE(resolver.get_resolved_macros().find(a_macro_name)
!= resolver.get_resolved_macros().end());
REQUIRE(resolver.get_resolved_macros().find(b_macro_name)
!= resolver.get_resolved_macros().end());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter));
// second run
REQUIRE(resolver.run(filter) == false);
REQUIRE(resolver.get_resolved_macros().empty());
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(expected_filter));
delete filter;
delete expected_filter;
}
}
TEST_CASE("Should find unknown macros", "[rule_loader]")
{
string macro_name = "test_macro";
SECTION("in the general case")
{
expr* filter = new and_expr({
new unary_check_expr("evt.name", "", "exists"),
new not_expr(
new value_expr(macro_name)
),
});
filter_macro_resolver resolver;
REQUIRE(resolver.run(filter) == false);
REQUIRE(resolver.get_unknown_macros().size() == 1);
REQUIRE(*resolver.get_unknown_macros().begin() == macro_name);
REQUIRE(resolver.get_resolved_macros().empty());
delete filter;
}
SECTION("with nested macros")
{
string a_macro_name = macro_name + "_1";
string b_macro_name = macro_name + "_2";
shared_ptr<expr> a_macro(new and_expr({
new unary_check_expr("one.field", "", "exists"),
new value_expr(b_macro_name),
}));
expr* filter = new value_expr(a_macro_name);
expr* expected_filter = clone(a_macro.get());
filter_macro_resolver resolver;
resolver.set_macro(a_macro_name, a_macro);
// first run
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == a_macro_name);
REQUIRE(resolver.get_unknown_macros().size() == 1);
REQUIRE(*resolver.get_unknown_macros().begin() == b_macro_name);
REQUIRE(filter->is_equal(expected_filter));
delete filter;
delete expected_filter;
}
}
TEST_CASE("Should undefine macro", "[rule_loader]")
{
string macro_name = "test_macro";
shared_ptr<expr> macro(new unary_check_expr("test.field", "", "exists"));
expr* a_filter = new value_expr(macro_name);
expr* b_filter = new value_expr(macro_name);
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
REQUIRE(resolver.run(a_filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(a_filter->is_equal(macro.get()));
resolver.set_macro(macro_name, NULL);
REQUIRE(resolver.run(b_filter) == false);
REQUIRE(resolver.get_resolved_macros().empty());
REQUIRE(resolver.get_unknown_macros().size() == 1);
REQUIRE(*resolver.get_unknown_macros().begin() == macro_name);
delete a_filter;
delete b_filter;
}
// checks that the macro AST is cloned and not shared across resolved filters
TEST_CASE("Should clone macro AST", "[rule_loader]")
{
string macro_name = "test_macro";
shared_ptr<unary_check_expr> macro(
new unary_check_expr("test.field", "", "exists"));
expr* filter = new value_expr(macro_name);
filter_macro_resolver resolver;
resolver.set_macro(macro_name, macro);
REQUIRE(resolver.run(filter) == true);
REQUIRE(resolver.get_resolved_macros().size() == 1);
REQUIRE(*resolver.get_resolved_macros().begin() == macro_name);
REQUIRE(resolver.get_unknown_macros().empty());
REQUIRE(filter->is_equal(macro.get()));
macro.get()->field = "another.field";
REQUIRE(!filter->is_equal(macro.get()));
delete filter;
}

View File

@@ -25,7 +25,6 @@ static uint16_t default_ruleset = 0;
static uint16_t non_default_ruleset = 3;
static uint16_t other_non_default_ruleset = 2;
static std::set<std::string> tags = {"some_tag", "some_other_tag"};
static std::set<uint16_t> evttypes = { ppm_event_type::PPME_GENERIC_E };
static std::shared_ptr<gen_event_filter> create_filter()
{
@@ -38,159 +37,220 @@ static std::shared_ptr<gen_event_filter> create_filter()
return ret;
}
TEST_CASE("Should enable/disable on ruleset", "[rulesets]")
TEST_CASE("Should enable/disable for exact match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
string source = "syscall";
r.add(source, rule_name, tags, evttypes, filter);
r.add(rule_name, tags, filter);
SECTION("Should enable/disable for exact match w/ default ruleset")
{
r.enable("one_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("one_rule", exact_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for exact match w/ specific ruleset")
{
r.enable("one_rule", exact_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
r.enable("one_rule", exact_match, disabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
}
SECTION("Should not enable for exact match different rule name")
{
r.enable("some_other_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for exact match w/ substring and default ruleset")
{
r.enable("one_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("one_rule", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should not enable for substring w/ exact_match")
{
r.enable("one_", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for prefix match w/ default ruleset")
{
r.enable("one_", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("one_", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for suffix match w/ default ruleset")
{
r.enable("_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("_rule", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for substring match w/ default ruleset")
{
r.enable("ne_ru", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("ne_ru", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for substring match w/ specific ruleset")
{
r.enable("ne_ru", substring_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
r.enable("ne_ru", substring_match, disabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
}
SECTION("Should enable/disable for tags w/ default ruleset")
{
std::set<std::string> want_tags = {"some_tag"};
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable_tags(want_tags, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
SECTION("Should enable/disable for tags w/ specific ruleset")
{
std::set<std::string> want_tags = {"some_tag"};
r.enable_tags(want_tags, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
r.enable_tags(want_tags, disabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
}
SECTION("Should not enable for different tags")
{
std::set<std::string> want_tags = {"some_different_tag"};
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
}
SECTION("Should enable/disable for overlapping tags")
{
std::set<std::string> want_tags = {"some_tag", "some_different_tag"};
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable_tags(want_tags, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
r.enable("one_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("one_rule", exact_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable on ruleset for incremental adding tags", "[rulesets]")
TEST_CASE("Should enable/disable for exact match w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
string source = "syscall";
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("one_rule", exact_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
r.enable("one_rule", exact_match, disabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
}
TEST_CASE("Should not enable for exact match different rule name", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("some_other_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for exact match w/ substring and default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("one_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("one_rule", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should not enable for substring w/ exact_match", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("one_", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for prefix match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("one_", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("one_", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for suffix match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("_rule", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for substring match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("ne_ru", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable("ne_ru", substring_match, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for substring match w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.enable("ne_ru", substring_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
r.enable("ne_ru", substring_match, disabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for tags w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag"};
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable_tags(want_tags, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for tags w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag"};
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
r.enable_tags(want_tags, disabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
REQUIRE(r.num_rules_for_ruleset(other_non_default_ruleset) == 0);
}
TEST_CASE("Should not enable for different tags", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_different_tag"};
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for overlapping tags", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag", "some_different_tag"};
r.add(rule_name, tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
r.enable_tags(want_tags, disabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
}
TEST_CASE("Should enable/disable for incremental adding tags", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> rule1_filter = create_filter();
string rule1_name = "one_rule";
std::set<std::string> rule1_tags = {"rule1_tag"};
r.add(source, rule1_name, rule1_tags, evttypes, rule1_filter);
r.add(rule1_name, rule1_tags, rule1_filter);
std::shared_ptr<gen_event_filter> rule2_filter = create_filter();
string rule2_name = "two_rule";
std::set<std::string> rule2_tags = {"rule2_tag"};
r.add(source, rule2_name, rule2_tags, evttypes, rule2_filter);
r.add(rule2_name, rule2_tags, rule2_filter);
std::set<std::string> want_tags;

View File

@@ -10,49 +10,52 @@
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
add_subdirectory(lua)
set(FALCO_ENGINE_SOURCE_FILES
rules.cpp
falco_common.cpp
falco_engine.cpp
falco_utils.cpp
json_evt.cpp
ruleset.cpp
formats.cpp
filter_macro_resolver.cpp
filter_evttype_resolver.cpp
rule_loader.cpp
rule_reader.cpp
stats_manager.cpp)
formats.cpp)
add_library(falco_engine STATIC ${FALCO_ENGINE_SOURCE_FILES})
add_dependencies(falco_engine njson string-view-lite)
add_dependencies(falco_engine njson lyaml lpeg string-view-lite)
if(USE_BUNDLED_DEPS)
add_dependencies(falco_engine yamlcpp)
add_dependencies(falco_engine libyaml)
endif()
if(MINIMAL_BUILD)
target_include_directories(
falco_engine
PUBLIC
"${LUAJIT_INCLUDE}"
"${NJSON_INCLUDE}"
"${TBB_INCLUDE_DIR}"
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${YAMLCPP_INCLUDE_DIR}"
"${PROJECT_BINARY_DIR}/userspace/engine")
"${PROJECT_BINARY_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/engine/lua")
else()
target_include_directories(
falco_engine
PUBLIC
"${LUAJIT_INCLUDE}"
"${NJSON_INCLUDE}"
"${CURL_INCLUDE_DIR}"
"${TBB_INCLUDE_DIR}"
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${YAMLCPP_INCLUDE_DIR}"
"${PROJECT_BINARY_DIR}/userspace/engine")
"${PROJECT_BINARY_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/engine/lua")
endif()
target_link_libraries(falco_engine "${FALCO_SINSP_LIBRARY}" "${YAMLCPP_LIB}")
target_link_libraries(falco_engine "${FALCO_SINSP_LIBRARY}" "${LPEG_LIB}" "${LYAML_LIB}" "${LIBYAML_LIB}" luafiles)
configure_file(config_falco_engine.h.in config_falco_engine.h)

View File

@@ -0,0 +1,20 @@
/*
Copyright (C) 2019 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#define FALCO_ENGINE_LUA_DIR "${FALCO_ABSOLUTE_SHARE_DIR}/lua/"
#define FALCO_ENGINE_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/userspace/engine/lua/"

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2022 The Falco Authors.
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.
@@ -14,9 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include "falco_common.h"
#include <fstream>
static vector<string> priority_names = {
#include "config_falco_engine.h"
#include "falco_common.h"
#include "banned.h" // This raises a compilation error when certain functions are used
#include "falco_engine_lua_files.hh"
std::vector<std::string> falco_common::priority_names = {
"Emergency",
"Alert",
"Critical",
@@ -24,59 +29,53 @@ static vector<string> priority_names = {
"Warning",
"Notice",
"Informational",
"Debug"
};
"Debug"};
bool falco_common::parse_priority(string v, priority_type& out)
falco_common::falco_common()
{
for (size_t i = 0; i < priority_names.size(); i++)
m_ls = lua_open();
if(!m_ls)
{
// note: for legacy reasons, "Info" and "Informational" has been used
// interchangeably and ambiguously, so this is the only edge case for
// which we can't apply strict equality check
if (!strcasecmp(v.c_str(), priority_names[i].c_str())
|| (i == PRIORITY_INFORMATIONAL && !strcasecmp(v.c_str(), "info")))
{
out = (priority_type) i;
return true;
}
throw falco_exception("Cannot open lua");
}
return false;
luaL_openlibs(m_ls);
}
falco_common::priority_type falco_common::parse_priority(string v)
falco_common::~falco_common()
{
falco_common::priority_type out;
if (!parse_priority(v, out))
if(m_ls)
{
throw falco_exception("Unknown priority value: " + v);
lua_close(m_ls);
}
return out;
}
bool falco_common::format_priority(priority_type v, string& out, bool shortfmt)
void falco_common::init()
{
if ((size_t) v < priority_names.size())
// Strings in the list lua_module_strings need to be loaded as
// lua modules, which also involves adding them to the
// package.module table.
for(const auto &pair : lua_module_strings)
{
if (v == PRIORITY_INFORMATIONAL && shortfmt)
{
out = "Info";
}
else
{
out = priority_names[(size_t) v];
}
return true;
}
return false;
}
lua_getglobal(m_ls, "package");
lua_getfield(m_ls, -1, "preload");
string falco_common::format_priority(priority_type v, bool shortfmt)
{
string out;
if(!format_priority(v, out, shortfmt))
{
throw falco_exception("Unknown priority enum value: " + to_string(v));
if(luaL_loadstring(m_ls, pair.first))
{
throw falco_exception("Failed to load embedded lua code " +
string(pair.second) + ": " + lua_tostring(m_ls, -1));
}
lua_setfield(m_ls, -2, pair.second);
}
return out;
}
// Strings in the list lua_code_strings need to be loaded and
// evaluated so any public functions can be directly called.
for(const auto &str : lua_code_strings)
{
if(luaL_loadstring(m_ls, str) || lua_pcall(m_ls, 0, 0, 0))
{
throw falco_exception("Failed to load + evaluate embedded lua code " +
string(str) + ": " + lua_tostring(m_ls, -1));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2022 The Falco Authors.
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.
@@ -19,6 +19,13 @@ limitations under the License.
#include <string>
#include <exception>
#include <mutex>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include <sinsp.h>
//
@@ -50,9 +57,22 @@ struct falco_exception : std::exception
std::string m_error_str;
};
namespace falco_common
//
// This is the base class of falco_engine/falco_output. It is
// responsible for managing a lua state and associated inspector and
// loading a single "main" lua file into that state.
//
class falco_common
{
const string syscall_source = "syscall";
public:
falco_common();
virtual ~falco_common();
void init();
// Priority levels, as a vector of strings
static std::vector<std::string> priority_names;
// Same as numbers/indices into the above vector
enum priority_type
@@ -66,9 +86,9 @@ namespace falco_common
PRIORITY_INFORMATIONAL = 6,
PRIORITY_DEBUG = 7
};
bool parse_priority(std::string v, priority_type& out);
priority_type parse_priority(std::string v);
bool format_priority(priority_type v, std::string& out, bool shortfmt=false);
std::string format_priority(priority_type v, bool shortfmt=false);
protected:
lua_State *m_ls;
std::mutex m_ls_semaphore;
};

View File

@@ -25,23 +25,39 @@ limitations under the License.
#include "falco_engine.h"
#include "falco_utils.h"
#include "falco_engine_version.h"
#include "rule_reader.h"
#include "config_falco_engine.h"
#include "formats.h"
extern "C" {
#include "lpeg.h"
#include "lyaml.h"
}
#include "utils.h"
#include "banned.h" // This raises a compilation error when certain functions are used
string lua_on_event = "on_event";
string lua_print_stats = "print_stats";
const std::string falco_engine::s_default_ruleset = "falco-default-ruleset";
using namespace std;
falco_engine::falco_engine(bool seed_rng)
falco_engine::falco_engine(bool seed_rng, const std::string& alternate_lua_dir)
: m_next_ruleset_id(0),
m_min_priority(falco_common::PRIORITY_DEBUG),
m_sampling_ratio(1), m_sampling_multiplier(0),
m_replace_container_info(false)
{
luaopen_lpeg(m_ls);
luaopen_yaml(m_ls);
falco_common::init();
falco_rules::init(m_ls);
m_required_plugin_versions.clear();
if(seed_rng)
{
srandom((unsigned) getpid());
@@ -52,9 +68,6 @@ falco_engine::falco_engine(bool seed_rng)
falco_engine::~falco_engine()
{
m_rules.clear();
m_rule_loader.clear();
m_rule_stats_manager.clear();
}
uint32_t falco_engine::engine_version()
@@ -69,7 +82,7 @@ static std::string fieldclass_key(const gen_event_filter_factory::filter_fieldcl
return fld_info.name + fld_info.shortdesc;
}
void falco_engine::list_fields(std::string &source, bool verbose, bool names_only, bool markdown)
void falco_engine::list_fields(std::string &source, bool verbose, bool names_only)
{
// Maps from field class name + short desc to list of event
// sources for which this field class can be used.
@@ -114,29 +127,18 @@ void falco_engine::list_fields(std::string &source, bool verbose, bool names_onl
seen_fieldclasses.insert(key);
if(names_only)
{
for(auto &field : fld_class.fields)
{
// Skip fields with the EPF_TABLE_ONLY flag.
if(field.tags.find("EPF_TABLE_ONLY") != field.tags.end())
{
continue;
}
printf("%s\n", field.name.c_str());
}
}
else if (markdown)
{
printf("%s\n", fld_class.as_markdown(
fieldclass_event_sources[fieldclass_key(fld_class)]).c_str());
}
else
if(!names_only)
{
printf("%s\n", fld_class.as_string(verbose,
fieldclass_event_sources[fieldclass_key(fld_class)]).c_str());
}
else
{
for(auto &field : fld_class.fields)
{
printf("%s\n", field.name.c_str());
}
}
}
}
}
@@ -150,45 +152,18 @@ void falco_engine::load_rules(const string &rules_content, bool verbose, bool al
void falco_engine::load_rules(const string &rules_content, bool verbose, bool all_events, uint64_t &required_engine_version)
{
rule_loader::configuration cfg(rules_content);
cfg.engine = this;
cfg.min_priority = m_min_priority;
cfg.output_extra = m_extra;
cfg.replace_output_container_info = m_replace_container_info;
if(!m_rules)
{
m_rules.reset(new falco_rules(this,
m_ls));
std::ostringstream os;
rule_reader reader;
bool success = reader.load(cfg, m_rule_loader);
if (success)
{
clear_filters();
m_rules.clear();
success = m_rule_loader.compile(cfg, m_rules);
}
if (!cfg.errors.empty())
{
os << cfg.errors.size() << " errors:" << std::endl;
for(auto &err : cfg.errors)
for(auto const &it : m_filter_factories)
{
os << err << std::endl;
m_rules->add_filter_factory(it.first, it.second);
}
}
if (!cfg.warnings.empty())
{
os << cfg.warnings.size() << " warnings:" << std::endl;
for(auto &warn : cfg.warnings)
{
os << warn << std::endl;
}
}
if(!success)
{
throw falco_exception(os.str());
}
if (verbose && os.str() != "") {
// todo(jasondellaluce): introduce a logging callback in Falco
fprintf(stderr, "When reading rules content: %s", os.str().c_str());
}
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority, required_engine_version, m_required_plugin_versions);
}
void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events)
@@ -223,7 +198,7 @@ void falco_engine::enable_rule(const string &substring, bool enabled, const stri
for(auto &it : m_rulesets)
{
it.ruleset->enable(substring, match_exact, enabled, ruleset_id);
it.second->enable(substring, match_exact, enabled, ruleset_id);
}
}
@@ -234,7 +209,7 @@ void falco_engine::enable_rule_exact(const string &rule_name, bool enabled, cons
for(auto &it : m_rulesets)
{
it.ruleset->enable(rule_name, match_exact, enabled, ruleset_id);
it.second->enable(rule_name, match_exact, enabled, ruleset_id);
}
}
@@ -244,7 +219,7 @@ void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled, con
for(auto &it : m_rulesets)
{
it.ruleset->enable_tags(tags, enabled, ruleset_id);
it.second->enable_tags(tags, enabled, ruleset_id);
}
}
@@ -274,7 +249,7 @@ uint64_t falco_engine::num_rules_for_ruleset(const std::string &ruleset)
uint64_t ret = 0;
for(auto &it : m_rulesets)
{
ret += it.ruleset->num_rules_for_ruleset(ruleset_id);
ret += it.second->num_rules_for_ruleset(ruleset_id);
}
return ret;
@@ -284,14 +259,14 @@ void falco_engine::evttypes_for_ruleset(std::string &source, std::set<uint16_t>
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
auto it = find_ruleset(source);
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
it->ruleset->evttypes_for_ruleset(evttypes, ruleset_id);
it->second->evttypes_for_ruleset(evttypes, ruleset_id);
}
@@ -309,145 +284,187 @@ std::shared_ptr<gen_event_formatter> falco_engine::create_formatter(const std::s
return it->second->create_formatter(output);
}
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id)
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::string &source, gen_event *ev, uint16_t ruleset_id)
{
if(should_drop_evt())
{
return unique_ptr<struct rule_result>();
}
try
{
auto &r = m_rulesets.at(source_idx);
if(!r.ruleset->run(ev, ruleset_id))
{
return unique_ptr<struct rule_result>();
}
unique_ptr<struct rule_result> res(new rule_result());
// note: indexes are 0-based, whereas check_ids are not
auto rule_idx = ev->get_check_id() - 1;
auto rule = m_rules.at(rule_idx);
if (!rule)
{
throw falco_exception("populate_rule_result error: unknown rule id "
+ to_string(rule_idx));
}
res->evt = ev;
res->rule = rule->name;
res->source = rule->source;
res->format = rule->output;
res->priority_num = rule->priority;
res->tags = rule->tags;
res->exception_fields = rule->exception_fields;
m_rule_stats_manager.on_event(m_rules, rule_idx);
return res;
}
catch(std::out_of_range const &exc)
{
std::string err = "Unknown event source index " + std::to_string(source_idx);
throw falco_exception(err);
}
}
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::size_t source_idx, gen_event *ev)
{
return process_event(source_idx, ev, m_default_ruleset_id);
}
std::size_t falco_engine::add_source(const std::string &source,
std::shared_ptr<gen_event_filter_factory> filter_factory,
std::shared_ptr<gen_event_formatter_factory> formatter_factory)
{
m_filter_factories[source] = filter_factory;
m_format_factories[source] = formatter_factory;
auto idx = m_rulesets.size();
m_rulesets.emplace_back(source, new falco_ruleset);
// here we just trust the caller they won't add the same source more than once
return idx;
}
std::shared_ptr<gen_event_filter_factory> falco_engine::get_filter_factory(
const std::string &source)
{
auto it = m_filter_factories.find(source);
if(it == m_filter_factories.end())
{
throw falco_exception(string("unknown event source: ") + source);
}
return it->second;
}
void falco_engine::describe_rule(string *rule)
{
static const char* rule_fmt = "%-50s %s\n";
fprintf(stdout, rule_fmt, "Rule", "Description");
fprintf(stdout, rule_fmt, "----", "-----------");
if (!rule)
{
for (auto &r : m_rules)
{
auto str = falco::utils::wrap_text(r.description, 51, 110) + "\n";
fprintf(stdout, rule_fmt, r.name.c_str(), str.c_str());
}
}
else
{
auto r = m_rules.at(*rule);
auto str = falco::utils::wrap_text(r->description, 51, 110) + "\n";
fprintf(stdout, rule_fmt, r->name.c_str(), str.c_str());
}
}
void falco_engine::print_stats()
{
string out;
m_rule_stats_manager.format(m_rules, out);
// todo(jasondellaluce): introduce a logging callback in Falco
fprintf(stdout, "%s", out.c_str());
}
void falco_engine::add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<uint16_t> &evttypes,
std::set<std::string> &tags)
{
auto it = find_ruleset(source);
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
it->ruleset->add(source, rule, tags, evttypes, filter);
if (!it->second->run(ev, ruleset_id))
{
return unique_ptr<struct rule_result>();
}
unique_ptr<struct rule_result> res(new rule_result());
res->source = source;
populate_rule_result(res, ev);
return res;
}
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::string &source, gen_event *ev)
{
return process_event(source, ev, m_default_ruleset_id);
}
void falco_engine::add_source(const std::string &source,
std::shared_ptr<gen_event_filter_factory> filter_factory,
std::shared_ptr<gen_event_formatter_factory> formatter_factory)
{
m_filter_factories[source] = filter_factory;
m_format_factories[source] = formatter_factory;
std::shared_ptr<falco_ruleset> ruleset(new falco_ruleset());
m_rulesets[source] = ruleset;
}
void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen_event *ev)
{
std::lock_guard<std::mutex> guard(m_ls_semaphore);
lua_getglobal(m_ls, lua_on_event.c_str());
if(lua_isfunction(m_ls, -1))
{
lua_pushnumber(m_ls, ev->get_check_id());
if(lua_pcall(m_ls, 1, 5, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function output: " + string(lerr);
throw falco_exception(err);
}
const char *p = lua_tostring(m_ls, -5);
res->rule = p;
res->evt = ev;
res->priority_num = (falco_common::priority_type) lua_tonumber(m_ls, -4);
res->format = lua_tostring(m_ls, -3);
// Tags are passed back as a table, and is on the top of the stack
lua_pushnil(m_ls); /* first key */
while (lua_next(m_ls, -2) != 0) {
// key is at index -2, value is at index
// -1. We want the value.
res->tags.insert(luaL_checkstring(m_ls, -1));
// Remove value, keep key for next iteration
lua_pop(m_ls, 1);
}
lua_pop(m_ls, 1); // Clean table leftover
// Exception fields are passed back as a table
lua_pushnil(m_ls); /* first key */
while (lua_next(m_ls, -2) != 0) {
// key is at index -2, value is at index
// -1. We want the keys.
res->exception_fields.insert(luaL_checkstring(m_ls, -2));
// Remove value, keep key for next iteration
lua_pop(m_ls, 1);
}
lua_pop(m_ls, 4);
}
else
{
throw falco_exception("No function " + lua_on_event + " found in lua compiler module");
}
}
void falco_engine::describe_rule(string *rule)
{
return m_rules->describe_rule(rule);
}
// Print statistics on the the rules that triggered
void falco_engine::print_stats()
{
lua_getglobal(m_ls, lua_print_stats.c_str());
if(lua_isfunction(m_ls, -1))
{
if(lua_pcall(m_ls, 0, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function print_stats: " + string(lerr);
throw falco_exception(err);
}
}
else
{
throw falco_exception("No function " + lua_print_stats + " found in lua rule loader module");
}
}
void falco_engine::add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<std::string> &tags)
{
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
it->second->add(rule, tags, filter);
}
bool falco_engine::is_source_valid(const std::string &source)
{
return (find_ruleset(source) != m_rulesets.end());
return (m_rulesets.find(source) != m_rulesets.end());
}
bool falco_engine::is_plugin_compatible(const std::string &name,
const std::string &version,
std::string &required_version)
{
return m_rule_loader.is_plugin_compatible(name, version, required_version);
sinsp_plugin::version plugin_version(version.c_str());
if(!plugin_version.m_valid)
{
throw falco_exception(string("Plugin version string ") + version + " not valid");
}
if(m_required_plugin_versions.find(name) == m_required_plugin_versions.end())
{
// No required engine versions, so no restrictions. Compatible.
return true;
}
for(auto &rversion : m_required_plugin_versions[name])
{
sinsp_plugin::version req_version(rversion.c_str());
if(req_version.m_version_major > plugin_version.m_version_major)
{
required_version = rversion;
return false;
}
}
return true;
}
void falco_engine::clear_filters()
{
for(auto &it : m_rulesets)
{
it.ruleset.reset(new falco_ruleset);
}
}
m_rulesets.clear();
void falco_engine::clear_loader()
{
m_rule_loader.clear();
for(auto &it : m_filter_factories)
{
std::shared_ptr<falco_ruleset> ruleset(new falco_ruleset());
m_rulesets[it.first] = ruleset;
}
m_required_plugin_versions.clear();
}
void falco_engine::set_sampling_ratio(uint32_t sampling_ratio)
@@ -481,17 +498,3 @@ inline bool falco_engine::should_drop_evt()
double coin = (random() * (1.0/RAND_MAX));
return (coin >= (1.0/(m_sampling_multiplier * m_sampling_ratio)));
}
inline std::vector<falco_engine::ruleset_node>::iterator falco_engine::find_ruleset(const std::string &source)
{
return std::find_if(
m_rulesets.begin(), m_rulesets.end(),
[&source](const ruleset_node &r) { return r.source == source; });
}
inline std::vector<falco_engine::ruleset_node>::const_iterator falco_engine::find_ruleset(const std::string &source) const
{
return std::find_if(
m_rulesets.cbegin(), m_rulesets.cend(),
[&source](const ruleset_node &r) { return r.source == source; });
}

View File

@@ -29,9 +29,10 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "gen_filter.h"
#include "rules.h"
#include "ruleset.h"
#include "rule_loader.h"
#include "stats_manager.h"
#include "config_falco_engine.h"
#include "falco_common.h"
//
@@ -40,10 +41,10 @@ limitations under the License.
// handled in a separate class falco_outputs.
//
class falco_engine
class falco_engine : public falco_common
{
public:
falco_engine(bool seed_rng=true);
falco_engine(bool seed_rng=true, const std::string& alternate_lua_dir=FALCO_ENGINE_SOURCE_LUA_DIR);
virtual ~falco_engine();
// A given engine has a version which identifies the fields
@@ -54,7 +55,7 @@ public:
// Print to stdout (using printf) a description of each field supported by this engine.
// If source is non-empty, only fields for the provided source are printed.
void list_fields(std::string &source, bool verbose, bool names_only, bool markdown);
void list_fields(std::string &source, bool verbose, bool names_only);
//
// Load rules either directly or from a filename.
@@ -118,15 +119,6 @@ public:
// Clear all existing filters.
void clear_filters();
//
// Clear all the definitions of the internal rule loader (e.g. defined
// rules, macros, lists, engine/plugin version requirements). This is meant
// to be used to free-up memory at runtime when the definitions are not
// used anymore. Calling this between successive invocations of load_rules
// or load_rules_file can cause failures of features like appending.
//
void clear_loader();
//
// Set the sampling ratio, which can affect which events are
// matched against the set of rules.
@@ -172,26 +164,20 @@ public:
// with a ruleset string.
//
// the returned rule_result is allocated and must be delete()d.
std::unique_ptr<rule_result> process_event(std::size_t source_idx, gen_event *ev, uint16_t ruleset_id);
std::unique_ptr<rule_result> process_event(std::string &source, gen_event *ev, uint16_t ruleset_id);
//
// Wrapper assuming the default ruleset
//
std::unique_ptr<rule_result> process_event(std::size_t source_idx, gen_event *ev);
std::unique_ptr<rule_result> process_event(std::string &source, gen_event *ev);
//
// Configure the engine to support events with the provided
// source, with the provided filter factory and formatter factory.
// Return source index for fast lookup.
//
std::size_t add_source(const std::string &source,
std::shared_ptr<gen_event_filter_factory> filter_factory,
std::shared_ptr<gen_event_formatter_factory> formatter_factory);
// todo(jasondellaluce): this is here for internal use, and
// will possibly be removed in the future
std::shared_ptr<gen_event_filter_factory> get_filter_factory(
const std::string &source);
void add_source(const std::string &source,
std::shared_ptr<gen_event_filter_factory> filter_factory,
std::shared_ptr<gen_event_formatter_factory> formatter_factory);
// Return whether or not there is a valid filter/formatter
// factory for this source.
@@ -203,7 +189,6 @@ public:
void add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<uint16_t> &evttypes,
std::set<std::string> &tags);
//
@@ -229,14 +214,6 @@ public:
bool is_plugin_compatible(const std::string &name, const std::string &version, std::string &required_version);
private:
struct ruleset_node
{
ruleset_node(const std::string &n, falco_ruleset *p):
source(n), ruleset(p) {}
std::string source;
mutable std::shared_ptr<falco_ruleset> ruleset;
};
//
// Determine whether the given event should be matched at all
@@ -245,9 +222,6 @@ private:
//
inline bool should_drop_evt();
inline std::vector<ruleset_node>::iterator find_ruleset(const std::string &source);
inline std::vector<ruleset_node>::const_iterator find_ruleset(const std::string &source) const;
// Maps from event source to object that can generate filters from rules
std::map<std::string, std::shared_ptr<gen_event_filter_factory>> m_filter_factories;
@@ -255,16 +229,18 @@ private:
std::map<std::string, std::shared_ptr<gen_event_formatter_factory>> m_format_factories;
// Maps from event source to the set of rules for that event source
std::vector<ruleset_node> m_rulesets;
rule_loader m_rule_loader;
indexed_vector<falco_rule> m_rules;
stats_manager m_rule_stats_manager;
std::map<std::string, std::shared_ptr<falco_ruleset>> m_rulesets;
std::unique_ptr<falco_rules> m_rules;
uint16_t m_next_ruleset_id;
std::map<string, uint16_t> m_known_rulesets;
falco_common::priority_type m_min_priority;
// Maps from plugin to a list of required plugin versions
// found in any loaded rules files.
std::map<std::string, std::list<std::string>> m_required_plugin_versions;
void populate_rule_result(unique_ptr<struct rule_result> &res, gen_event *ev);
//
// Here's how the sampling ratio and multiplier influence

View File

@@ -16,9 +16,10 @@ limitations under the License.
// The version of rules/filter fields/etc supported by this Falco
// engine.
#define FALCO_ENGINE_VERSION (12)
#define FALCO_ENGINE_VERSION (11)
// This is the result of running "falco --list -N | sha256sum" and
// represents the fields supported by this version of Falco. It's used
// at build time to detect a changed set of fields.
#define FALCO_FIELDS_CHECKSUM "77c4c549181b8aac1b9698c0101ac61acb5b2faede84a2c4fecb34834c6de2b9"
#define FALCO_FIELDS_CHECKSUM "4de812495f8529ac20bda2b9774462b15911a51df293d59fe9ccb6b922fdeb9d"

View File

@@ -17,7 +17,6 @@ limitations under the License.
*/
#include <cstring>
#include <iomanip>
#include "falco_utils.h"
#include "banned.h" // This raises a compilation error when certain functions are used
@@ -28,29 +27,6 @@ namespace falco
namespace utils
{
std::string wrap_text(const std::string& in, uint32_t indent, uint32_t line_len)
{
std::istringstream is(in);
std::ostringstream os;
std::string word;
uint32_t len = 0;
while (is >> word)
{
if((len + word.length() + 1) <= (line_len-indent))
{
len += word.length() + 1;
}
else
{
os << std::endl;
os << std::left << std::setw(indent) << " ";
len = word.length() + 1;
}
os << word << " ";
}
return os.str();
}
uint32_t hardware_concurrency()
{
auto hc = std::thread::hardware_concurrency();

View File

@@ -40,7 +40,7 @@ namespace falco
namespace utils
{
std::string wrap_text(const std::string& in, uint32_t indent, uint32_t linelen);
std::string wrap_text(const std::string& str, uint32_t initial_pos, uint32_t indent, uint32_t line_len);
void readfile(const std::string& filename, std::string& data);

View File

@@ -1,164 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "filter_evttype_resolver.h"
#include <sinsp.h>
using namespace std;
using namespace libsinsp::filter;
extern sinsp_evttables g_infotables;
static bool is_evttype_operator(const string& op)
{
return op == "==" || op == "=" || op == "!=" || op == "in";
}
void filter_evttype_resolver::inversion(set<uint16_t>& types)
{
set<uint16_t> all_types;
evttypes("", all_types);
if (types != all_types) // we don't invert the "all types" set
{
set<uint16_t> diff = types;
types.clear();
set_difference(
all_types.begin(), all_types.end(), diff.begin(), diff.end(),
inserter(types, types.begin()));
}
}
void filter_evttype_resolver::evttypes(string evtname, set<uint16_t>& out)
{
// Fill in from 2 to PPM_EVENT_MAX-1. 0 and 1 are excluded as
// those are PPM_GENERIC_E/PPME_GENERIC_X
const struct ppm_event_info* etable = g_infotables.m_event_info;
for(uint16_t i = 2; i < PPM_EVENT_MAX; i++)
{
// Skip "old" event versions, unused events, or events not matching
// the requested evtname
if(!(etable[i].flags & (EF_OLD_VERSION | EF_UNUSED))
&& (evtname.empty() || string(etable[i].name) == evtname))
{
out.insert(i);
}
}
}
void filter_evttype_resolver::evttypes(ast::expr* filter, set<uint16_t>& out)
{
m_expect_value = false;
m_last_node_evttypes.clear();
filter->accept(this);
out.insert(m_last_node_evttypes.begin(), m_last_node_evttypes.end());
}
void filter_evttype_resolver::evttypes(
shared_ptr<ast::expr> filter, set<uint16_t>& out)
{
m_expect_value = false;
m_last_node_evttypes.clear();
filter.get()->accept(this);
out.insert(m_last_node_evttypes.begin(), m_last_node_evttypes.end());
}
// "and" nodes evttypes are the intersection of the evttypes of their children.
// we initialize the set with "all event types"
void filter_evttype_resolver::visit(ast::and_expr* e)
{
set<uint16_t> types, inters;
evttypes("", types);
m_last_node_evttypes.clear();
for (auto &c : e->children)
{
inters.clear();
c->accept(this);
set_intersection(
types.begin(), types.end(),
m_last_node_evttypes.begin(), m_last_node_evttypes.end(),
inserter(inters, inters.begin()));
types = inters;
}
m_last_node_evttypes = types;
}
// "or" nodes evttypes are the union of the evttypes their children
void filter_evttype_resolver::visit(ast::or_expr* e)
{
set<uint16_t> types;
m_last_node_evttypes.clear();
for (auto &c : e->children)
{
c->accept(this);
types.insert(m_last_node_evttypes.begin(), m_last_node_evttypes.end());
}
m_last_node_evttypes = types;
}
void filter_evttype_resolver::visit(ast::not_expr* e)
{
m_last_node_evttypes.clear();
e->child->accept(this);
inversion(m_last_node_evttypes);
}
void filter_evttype_resolver::visit(ast::binary_check_expr* e)
{
m_last_node_evttypes.clear();
if (e->field == "evt.type" && is_evttype_operator(e->op))
{
m_expect_value = true;
e->value->accept(this);
m_expect_value = false;
if (e->op == "!=")
{
inversion(m_last_node_evttypes);
}
return;
}
evttypes("", m_last_node_evttypes);
}
void filter_evttype_resolver::visit(ast::unary_check_expr* e)
{
m_last_node_evttypes.clear();
evttypes("", m_last_node_evttypes);
}
void filter_evttype_resolver::visit(ast::value_expr* e)
{
m_last_node_evttypes.clear();
if (m_expect_value)
{
evttypes(e->value, m_last_node_evttypes);
return;
}
evttypes("", m_last_node_evttypes);
}
void filter_evttype_resolver::visit(ast::list_expr* e)
{
m_last_node_evttypes.clear();
if (m_expect_value)
{
for (auto &v : e->values)
{
evttypes(v, m_last_node_evttypes);
}
return;
}
evttypes("", m_last_node_evttypes);
}

View File

@@ -1,69 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <filter/parser.h>
#include <string>
#include <set>
#include <memory>
/*!
\brief Helper class for finding event types
*/
class filter_evttype_resolver: private libsinsp::filter::ast::expr_visitor
{
public:
/*!
\brief Collects the evttypes related to the provided event name.
The event types are inserted in the set provided as parameter.
The set is not cleared before inserting the elements.
\param evtname The event name used to search event types. If an empty
string is passed, all the available evttypes are collected
\param out The set to be filled with the evttypes
*/
void evttypes(std::string evtname, std::set<uint16_t>& out);
/*!
\brief Visits a filter AST and collects all the evttypes for which
the filter expression can be evaluated as true. The event types are
inserted in the set provided as parameter. The set is not cleared before
inserting the elements.
\param filter The filter AST to be explored
\param out The set to be filled with the evttypes
*/
void evttypes(libsinsp::filter::ast::expr* filter, std::set<uint16_t>& out);
/*!
\brief Overloaded version of evttypes() that supports filters wrapped
in shared pointers
*/
void evttypes(std::shared_ptr<libsinsp::filter::ast::expr> filter,
std::set<uint16_t>& out);
private:
void visit(libsinsp::filter::ast::and_expr* e) override;
void visit(libsinsp::filter::ast::or_expr* e) override;
void visit(libsinsp::filter::ast::not_expr* e) override;
void visit(libsinsp::filter::ast::value_expr* e) override;
void visit(libsinsp::filter::ast::list_expr* e) override;
void visit(libsinsp::filter::ast::unary_check_expr* e) override;
void visit(libsinsp::filter::ast::binary_check_expr* e) override;
void inversion(std::set<uint16_t>& types);
bool m_expect_value;
std::set<uint16_t> m_last_node_evttypes;
};

View File

@@ -1,149 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "filter_macro_resolver.h"
using namespace std;
using namespace libsinsp::filter;
bool filter_macro_resolver::run(libsinsp::filter::ast::expr*& filter)
{
m_unknown_macros.clear();
m_resolved_macros.clear();
m_last_node_changed = false;
m_last_node = filter;
filter->accept(this);
if (m_last_node_changed)
{
delete filter;
filter = m_last_node;
}
return !m_resolved_macros.empty();
}
bool filter_macro_resolver::run(std::shared_ptr<libsinsp::filter::ast::expr>& filter)
{
m_unknown_macros.clear();
m_resolved_macros.clear();
m_last_node_changed = false;
m_last_node = filter.get();
filter->accept(this);
if (m_last_node_changed)
{
filter.reset(m_last_node);
}
return !m_resolved_macros.empty();
}
void filter_macro_resolver::set_macro(
string name,
shared_ptr<libsinsp::filter::ast::expr> macro)
{
m_macros[name] = macro;
}
set<string>& filter_macro_resolver::get_unknown_macros()
{
return m_unknown_macros;
}
set<string>& filter_macro_resolver::get_resolved_macros()
{
return m_resolved_macros;
}
void filter_macro_resolver::visit(ast::and_expr* e)
{
for (size_t i = 0; i < e->children.size(); i++)
{
e->children[i]->accept(this);
if (m_last_node_changed)
{
delete e->children[i];
e->children[i] = m_last_node;
}
}
m_last_node = e;
m_last_node_changed = false;
}
void filter_macro_resolver::visit(ast::or_expr* e)
{
for (size_t i = 0; i < e->children.size(); i++)
{
e->children[i]->accept(this);
if (m_last_node_changed)
{
delete e->children[i];
e->children[i] = m_last_node;
}
}
m_last_node = e;
m_last_node_changed = false;
}
void filter_macro_resolver::visit(ast::not_expr* e)
{
e->child->accept(this);
if (m_last_node_changed)
{
delete e->child;
e->child = m_last_node;
}
m_last_node = e;
m_last_node_changed = false;
}
void filter_macro_resolver::visit(ast::list_expr* e)
{
m_last_node = e;
m_last_node_changed = false;
}
void filter_macro_resolver::visit(ast::binary_check_expr* e)
{
// avoid exploring checks, so that we can be sure that each
// value_expr* node visited is a macro identifier
m_last_node = e;
m_last_node_changed = false;
}
void filter_macro_resolver::visit(ast::unary_check_expr* e)
{
m_last_node = e;
m_last_node_changed = false;
}
void filter_macro_resolver::visit(ast::value_expr* e)
{
// we are supposed to get here only in case
// of identier-only children from either a 'not',
// an 'and' or an 'or'.
auto macro = m_macros.find(e->value);
if (macro != m_macros.end() && macro->second) // skip null-ptr macros
{
ast::expr* new_node = ast::clone(macro->second.get());
new_node->accept(this); // this sets m_last_node
m_last_node_changed = true;
m_resolved_macros.insert(e->value);
}
else
{
m_last_node = e;
m_last_node_changed = false;
m_unknown_macros.insert(e->value);
}
}

View File

@@ -1,93 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <filter/parser.h>
#include <string>
#include <set>
#include <map>
#include <memory>
/*!
\brief Helper class for substituting and resolving macro
references in parsed filters.
*/
class filter_macro_resolver: private libsinsp::filter::ast::expr_visitor
{
public:
/*!
\brief Visits a filter AST and substitutes macro references
according with all the definitions added through set_macro(),
by replacing the reference with a clone of the macro AST.
\param filter The filter AST to be processed. Note that the pointer
is passed by reference and be modified in order to apply
the substutions. In that case, the old pointer is owned by this
class and is deleted automatically.
\return true if at least one of the defined macros is resolved
*/
bool run(libsinsp::filter::ast::expr*& filter);
/*!
\brief Version of run() that works with shared pointers
*/
bool run(std::shared_ptr<libsinsp::filter::ast::expr>& filter);
/*!
\brief Defines a new macro to be substituted in filters. If called
multiple times for the same macro name, the previous definition
gets overridden. A macro can be undefined by setting a null
AST pointer.
\param name The name of the macro.
\param macro The AST of the macro.
*/
void set_macro(
std::string name,
std::shared_ptr<libsinsp::filter::ast::expr> macro);
/*!
\brief Returns a set containing the names of all the macros
substituted during the last invocation of run(). Should be
non-empty if the last invocation of run() returned true.
*/
std::set<std::string>& get_resolved_macros();
/*!
\brief Returns a set containing the names of all the macros
that remained unresolved during the last invocation of run().
A macro remains unresolved if it is found inside the processed
filter but it was not defined with set_macro();
*/
std::set<std::string>& get_unknown_macros();
private:
void visit(libsinsp::filter::ast::and_expr* e) override;
void visit(libsinsp::filter::ast::or_expr* e) override;
void visit(libsinsp::filter::ast::not_expr* e) override;
void visit(libsinsp::filter::ast::value_expr* e) override;
void visit(libsinsp::filter::ast::list_expr* e) override;
void visit(libsinsp::filter::ast::unary_check_expr* e) override;
void visit(libsinsp::filter::ast::binary_check_expr* e) override;
bool m_last_node_changed;
libsinsp::filter::ast::expr* m_last_node;
std::set<std::string> m_unknown_macros;
std::set<std::string> m_resolved_macros;
std::map<
std::string,
std::shared_ptr<libsinsp::filter::ast::expr>
> m_macros;
};

View File

@@ -18,7 +18,16 @@ limitations under the License.
#include <string>
#include <map>
extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include <gen_filter.h>
#include "falco_engine.h"
class falco_formats

View File

@@ -1,133 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <map>
#include <string>
#include <vector>
/*!
\brief Simple wrapper of std::vector that allows random access
through both numeric and string indexes with O(1) complexity
*/
template <typename T>
class indexed_vector
{
public:
/*!
\brief Returns the number of elements
*/
virtual inline size_t size()
{
return m_entries.size();
}
/*!
\brief Returns true if the vector is empty
*/
virtual inline bool empty()
{
return m_entries.empty();
}
/*!
\brief Removes all the elements
*/
virtual inline void clear()
{
m_entries.clear();
m_index.clear();
}
/*!
\brief Inserts a new element in the vector with a given string index
and returns its numeric index. String indexes are unique in
the vector. If no element is already present with the given string
index, then the provided element is added to the vector and its
numeric index is assigned as the next free slot in the vector.
Otherwise, the existing element gets overwritten with the contents
of the provided one and the numeric index of the existing element
is returned.
\param entry Element to add in the vector
\param index String index of the element to be added in the vector
\return The numeric index assigned to the element
*/
virtual inline size_t insert(T& entry, const std::string& index)
{
size_t id;
auto prev = m_index.find(index);
if (prev != m_index.end()) {
id = prev->second;
m_entries[id] = entry;
return id;
}
id = m_entries.size();
m_entries.push_back(entry);
m_index[index] = id;
return id;
}
/*!
\brief Returns a pointer to the element at the given numeric index,
or nullptr if no element exists at the given index.
*/
virtual inline T* at(size_t id) const
{
if (id <= m_entries.size())
{
return (T* const) &m_entries[id];
}
return nullptr;
}
/*!
\brief Returns a pointer to the element at the given string index,
or nullptr if no element exists at the given index.
*/
virtual inline T* at(const std::string& index) const
{
auto it = m_index.find(index);
if (it != m_index.end()) {
return at(it->second);
}
return nullptr;
}
virtual inline typename std::vector<T>::iterator begin()
{
return m_entries.begin();
}
virtual inline typename std::vector<T>::iterator end()
{
return m_entries.end();
}
virtual inline typename std::vector<T>::const_iterator begin() const
{
return m_entries.begin();
}
virtual inline typename std::vector<T>::const_iterator end() const
{
return m_entries.end();
}
private:
std::vector<T> m_entries;
std::map<std::string, size_t> m_index;
};

View File

@@ -591,15 +591,13 @@ const json_event_filter_check::values_t &json_event_filter_check::extracted_valu
bool json_event_filter_check::compare(gen_event *evt)
{
auto jevt = (json_event *) evt;
std::vector<extract_value_t> values;
if (!extract(jevt, values))
{
return false;
}
auto evalues = (const extracted_values_t *) values[0].ptr;
auto jevt = (json_event *)evt;
uint32_t len;
auto evalues = (const extracted_values_t *) extract(jevt, &len);
values_set_t setvals;
switch(m_cmpop)
{
case CO_EQ:
@@ -714,7 +712,7 @@ void json_event_filter_check::add_extracted_value_num(int64_t val)
m_evalues.second.emplace(json_event_value(val));
}
bool json_event_filter_check::extract(gen_event *evt, std::vector<extract_value_t>& values, bool sanitize_strings)
uint8_t *json_event_filter_check::extract(gen_event *evt, uint32_t *len, bool sanitize_strings)
{
m_evalues.first.clear();
m_evalues.second.clear();
@@ -725,8 +723,9 @@ bool json_event_filter_check::extract(gen_event *evt, std::vector<extract_value_
m_evalues.second.clear();
add_extracted_value(no_value);
}
values.push_back({(uint8_t *)&m_evalues, sizeof(m_evalues)});
return true;
*len = sizeof(m_evalues);
return (uint8_t *)&m_evalues;
}
bool json_event_filter_check::extract_values(json_event *jevt)
@@ -1418,7 +1417,7 @@ json_event_filter_check *k8s_audit_filter_check::allocate_new()
return (json_event_filter_check *)chk;
}
json_event_filter::json_event_filter(): sinsp_filter(NULL)
json_event_filter::json_event_filter()
{
}
@@ -1530,9 +1529,12 @@ void json_event_formatter::set_format(output_format of, const std::string &forma
bool json_event_formatter::tostring_withformat(gen_event *gevt, std::string &output, gen_event_formatter::output_format of)
{
json_event *ev = static_cast<json_event *>(gevt);
std::string ret;
if(of == OF_JSON)
{
output = tojson(ev);
ret = tojson(ev);
return true;
}
else
@@ -1660,13 +1662,13 @@ void json_event_formatter::parse_format()
void json_event_formatter::resolve_format(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved)
{
vector<extract_value_t> values;
for(auto tok : m_tokens)
{
if(tok.check)
{
values.clear();
tok.check->extract(ev, values);
uint32_t len;
(void) tok.check->extract(ev, &len);
const json_event_filter_check::values_t &evals =
tok.check->extracted_values();

View File

@@ -27,7 +27,7 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "prefix_search.h"
#include <sinsp.h>
#include "gen_filter.h"
class json_event : public gen_event
{
@@ -179,16 +179,8 @@ public:
void add_filter_value(const char *str, uint32_t len, uint32_t i = 0);
bool compare(gen_event *evt);
// This is adapted to support the new extract() method signature that
// supports extracting list of values, however json_evt was implemented
// to support this feature in the first place through the
// extracted_values_t structure. As such, for now this is only used for
// signature compliance, and always pushes a single value. The value pushed
// in the vector is a a const extracted_values_t* that points to the
// internal m_evalues. This is a temporary workaround to sync with the
// latest falcosecurity/libs development without re-designing the whole K8S
// support, which will eventually be refactored as a plugin in the future anyway.
bool extract(gen_event *evt, std::vector<extract_value_t>& values, bool sanitize_strings = true) final;
// This always returns a const extracted_values_t *. The pointer points to m_evalues;
uint8_t* extract(gen_event *evt, uint32_t* len, bool sanitize_strings = true) final;
const std::string &field();
const std::string &idx();
@@ -286,7 +278,7 @@ private:
// events. See alias struct for usage.
std::list<nlohmann::json::json_pointer> m_jptrs;
// The extraction function to use. May not be defined, in which
// Theextraction function to use. May not be defined, in which
// case the default function is used.
extract_t m_extract;
@@ -383,8 +375,7 @@ public:
json_event_filter_check &jchk);
};
class json_event_filter : public sinsp_filter
class json_event_filter : public gen_event_filter
{
public:
json_event_filter();

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2022 The Falco Authors.
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.
@@ -16,17 +16,7 @@ limitations under the License.
#pragma once
#include <set>
#include <string>
#include "falco_common.h"
#include "lua.h"
int luaopen_lpeg (lua_State *L);
struct falco_rule
{
std::string source;
std::string name;
std::string description;
std::string output;
std::set<std::string> tags;
std::set<std::string> exception_fields;
falco_common::priority_type priority;
};

View File

@@ -1,5 +1,5 @@
#
# Copyright (C) 2022 The Falco Authors.
# Copyright (C) 2021 The Falco Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
@@ -9,15 +9,13 @@
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
set(CXXOPTS_SRC "${PROJECT_BINARY_DIR}/cxxopts-prefix/src/cxxopts/")
set(CXXOPTS_INCLUDE_DIR "${CXXOPTS_SRC}/include")
file(GLOB_RECURSE lua_module_files ${CMAKE_CURRENT_SOURCE_DIR} *.lua)
ExternalProject_Add(
cxxopts
URL "https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.0.0.tar.gz"
URL_HASH "SHA256=36f41fa2a46b3c1466613b63f3fa73dc24d912bc90d667147f1e43215a8c6d00"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/falco_engine_lua_files.cpp
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/lua-to-cpp.sh ${CMAKE_CURRENT_SOURCE_DIR} ${LYAML_LUA_DIR} ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${lua_files} ${CMAKE_CURRENT_SOURCE_DIR}/lua-to-cpp.sh lyaml)
add_library(luafiles falco_engine_lua_files.cpp)
target_include_directories(luafiles PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,6 @@
Installation
------------
The grammar uses the `lpeg` parser. For now install it using luarocks:
`luarocks install lpeg`.

View File

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

View File

@@ -0,0 +1,260 @@
-- 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 compiler = {}
compiler.trim = parser.trim
function map(f, arr)
local res = {}
for i,v in ipairs(arr) do
res[i] = f(v)
end
return res
end
function foldr(f, acc, arr)
for i,v in pairs(arr) do
acc = f(acc, v)
end
return acc
end
--[[
Given a map of macro definitions, traverse AST and replace macro references
with their definitions.
The AST is changed in-place.
The return value is a boolean which is true if any macro was
substitued. This allows a caller to re-traverse until no more macros are
found, a simple strategy for recursive resoltuions (e.g. when a macro
definition uses another macro).
--]]
function copy_ast_obj(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[copy_ast_obj(k)] = copy_ast_obj(v) end
return res
end
function expand_macros(ast, defs, changed)
if (ast.type == "Rule") then
return expand_macros(ast.filter, defs, changed)
elseif ast.type == "Filter" then
if (ast.value.type == "Macro") then
if (defs[ast.value.value] == nil) then
return false, "Undefined macro '".. ast.value.value .. "' used in filter."
end
defs[ast.value.value].used = true
ast.value = copy_ast_obj(defs[ast.value.value].ast)
changed = true
return true, changed
end
return expand_macros(ast.value, defs, changed)
elseif ast.type == "BinaryBoolOp" then
if (ast.left.type == "Macro") then
if (defs[ast.left.value] == nil) then
return false, "Undefined macro '".. ast.left.value .. "' used in filter."
end
defs[ast.left.value].used = true
ast.left = copy_ast_obj(defs[ast.left.value].ast)
changed = true
end
if (ast.right.type == "Macro") then
if (defs[ast.right.value] == nil) then
return false, "Undefined macro ".. ast.right.value .. " used in filter."
end
defs[ast.right.value].used = true
ast.right = copy_ast_obj(defs[ast.right.value].ast)
changed = true
end
local status, changed_left = expand_macros(ast.left, defs, false)
if status == false then
return false, changed_left
end
local status, changed_right = expand_macros(ast.right, defs, false)
if status == false then
return false, changed_right
end
return true, changed or changed_left or changed_right
elseif ast.type == "UnaryBoolOp" then
if (ast.argument.type == "Macro") then
if (defs[ast.argument.value] == nil) then
return false, "Undefined macro ".. ast.argument.value .. " used in filter."
end
defs[ast.argument.value].used = true
ast.argument = copy_ast_obj(defs[ast.argument.value].ast)
changed = true
end
return expand_macros(ast.argument, defs, changed)
end
return true, changed
end
function get_macros(ast, set)
if (ast.type == "Macro") then
set[ast.value] = true
return set
end
if ast.type == "Filter" then
return get_macros(ast.value, set)
end
if ast.type == "BinaryBoolOp" then
local left = get_macros(ast.left, {})
local right = get_macros(ast.right, {})
for m, _ in pairs(left) do set[m] = true end
for m, _ in pairs(right) do set[m] = true end
return set
end
if ast.type == "UnaryBoolOp" then
return get_macros(ast.argument, set)
end
return set
end
function get_filters(ast)
local filters = {}
function cb(node)
if node.type == "FieldName" then
filters[node.value] = 1
end
end
parser.traverse_ast(ast.filter.value, {FieldName=1} , cb)
return filters
end
function compiler.expand_lists_in(source, list_defs)
for name, def in pairs(list_defs) do
local bpos = string.find(source, name, 1, true)
while bpos ~= nil do
def.used = true
local epos = bpos + string.len(name)
-- 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
new_source = ""
if bpos > 1 then
new_source = new_source..string.sub(source, 1, bpos-1)
end
sub = table.concat(def.items, ", ")
new_source = new_source..sub
if epos <= string.len(source) then
new_source = new_source..string.sub(source, epos, string.len(source))
end
source = new_source
bpos = bpos + (string.len(sub)-string.len(name))
end
bpos = string.find(source, name, bpos+1, true)
end
end
return source
end
function compiler.compile_macro(line, macro_defs, list_defs)
line = compiler.expand_lists_in(line, list_defs)
local ast, error_msg = parser.parse_filter(line)
if (error_msg) then
msg = "Compilation error when compiling \""..line.."\": ".. error_msg
return false, msg
end
-- Simply as a validation step, try to expand all macros in this
-- macro's condition. This changes the ast, so we make a copy
-- first.
local ast_copy = copy_ast_obj(ast)
if (ast.type == "Rule") then
-- Line is a filter, so expand macro references
repeat
status, expanded = expand_macros(ast_copy, macro_defs, false)
if status == false then
msg = "Compilation error when compiling \""..line.."\": ".. expanded
return false, msg
end
until expanded == false
else
return false, "Unexpected top-level AST type: "..ast.type
end
return true, ast
end
--[[
Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST.
--]]
function compiler.compile_filter(name, source, macro_defs, list_defs)
source = compiler.expand_lists_in(source, list_defs)
local ast, error_msg = parser.parse_filter(source)
if (error_msg) then
msg = "Compilation error when compiling \""..source.."\": "..error_msg
return false, msg
end
if (ast.type == "Rule") then
-- Line is a filter, so expand macro references
repeat
status, expanded = expand_macros(ast, macro_defs, false)
if status == false then
return false, expanded
end
until expanded == false
else
return false, "Unexpected top-level AST type: "..ast.type
end
filters = get_filters(ast)
return true, ast, filters
end
return compiler

View File

@@ -0,0 +1,307 @@
-- 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.
--
--[[
Falco grammar and parser.
Much of the scaffolding and helpers was derived from Andre Murbach Maidl's Lua parser (https://github.com/andremm/lua-parser).
While this is based on the falcosecurity-libs filtering syntax (*), the Falco syntax is extended to support "macro" terms, which are just identifiers.
(*) There is currently one known difference with the syntax implemented in libsinsp: In libsinsp, field names cannot start with 'a', 'o', or 'n'. With this parser they can.
--]]
local parser = {}
local lpeg = require "lpeg"
lpeg.locale(lpeg)
local P, S, V = lpeg.P, lpeg.S, lpeg.V
local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc
local Cf, Cg, Cmt, Cp, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Ct
local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum
local xdigit = lpeg.xdigit
local space = lpeg.space
-- error message auxiliary functions
-- creates an error message for the input string
local function syntaxerror(errorinfo, pos, msg)
local error_msg = "%s: syntax error, %s"
return string.format(error_msg, pos, msg)
end
-- gets the farthest failure position
local function getffp(s, i, t)
return t.ffp or i, t
end
-- gets the table that contains the error information
local function geterrorinfo()
return Cmt(Carg(1), getffp) * (C(V "OneWord") + Cc("EOF")) / function(t, u)
t.unexpected = u
return t
end
end
-- creates an errror message using the farthest failure position
local function errormsg()
return geterrorinfo() / function(t)
local p = t.ffp or 1
local msg = "unexpected '%s', expecting %s"
msg = string.format(msg, t.unexpected, t.expected)
return nil, syntaxerror(t, p, msg)
end
end
-- reports a syntactic error
local function report_error()
return errormsg()
end
--- sets the farthest failure position and the expected tokens
local function setffp(s, i, t, n)
if not t.ffp or i > t.ffp then
t.ffp = i
t.list = {}
t.list[n] = n
t.expected = "'" .. n .. "'"
elseif i == t.ffp then
if not t.list[n] then
t.list[n] = n
t.expected = "'" .. n .. "', " .. t.expected
end
end
return false
end
local function updateffp(name)
return Cmt(Carg(1) * Cc(name), setffp)
end
-- regular combinators and auxiliary functions
local function token(pat, name)
return pat * V "Skip" + updateffp(name) * P(false)
end
local function symb(str)
return token(P(str), str)
end
local function kw(str)
return token(P(str) * -V "idRest", str)
end
local function list(pat, sep)
return Ct(pat ^ -1 * (sep * pat ^ 0) ^ 0) / function(elements)
return {type = "List", elements = elements}
end
end
--http://lua-users.org/wiki/StringTrim
function trim(s)
if (type(s) ~= "string") then
return s
end
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
parser.trim = trim
local function terminal(tag)
-- 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)
val = tok
if tag ~= "String" then
val = trim(tok)
end
return {type = tag, value = val}
end
end
local function unaryboolop(op, e)
return {type = "UnaryBoolOp", operator = op, argument = e}
end
local function unaryrelop(e, op)
return {type = "UnaryRelOp", operator = op, argument = e}
end
local function binaryop(e1, op, e2)
if not op then
return e1
else
return {type = "BinaryBoolOp", operator = op, left = e1, right = e2}
end
end
local function bool(pat, sep)
return Cf(pat * Cg(sep * pat) ^ 0, binaryop)
end
local function rel(left, sep, right)
return left * sep * right / function(e1, op, e2)
return {type = "BinaryRelOp", operator = op, left = e1, right = e2}
end
end
-- grammar
local function filter(e)
return {type = "Filter", value = e}
end
local function rule(filter)
return {type = "Rule", filter = filter}
end
local G = {
V "Start", -- Entry rule
Start = V "Skip" * (V "Comment" + V "Rule" / rule) ^ -1 * -1 + report_error(),
-- Grammar
Comment = P "#" * P(1) ^ 0,
Rule = V "Filter" / filter * ((V "Skip") ^ -1),
Filter = V "OrExpression",
OrExpression = bool(V "AndExpression", V "OrOp"),
AndExpression = bool(V "NotExpression", V "AndOp"),
NotExpression = V "UnaryBoolOp" * V "NotExpression" / unaryboolop + V "ExistsExpression",
ExistsExpression = terminal "FieldName" * V "ExistsOp" / unaryrelop + V "MacroExpression",
MacroExpression = terminal "Macro" + V "RelationalExpression",
RelationalExpression = rel(terminal "FieldName", V "RelOp", V "Value") +
rel(terminal "FieldName", V "SetOp", V "InList") +
V "PrimaryExp",
PrimaryExp = symb("(") * V "Filter" * symb(")"),
FuncArgs = symb("(") * list(V "Value", symb(",")) * symb(")"),
-- Terminals
Value = terminal "Number" + terminal "String" + terminal "BareString",
InList = symb("(") * list(V "Value", symb(",")) * symb(")"),
-- Lexemes
Space = space ^ 1,
Skip = (V "Space") ^ 0,
idStart = alpha + P("_"),
idRest = alnum + P("_"),
Identifier = V "idStart" * V "idRest" ^ 0,
Macro = V "idStart" * V "idRest" ^ 0 * -P ".",
Int = digit ^ 1,
ArgString = (alnum + S ",.-_/*?~") ^ 1,
PortRangeString = (V "Int" + S ":,") ^ 1,
Index = V "PortRangeString" + V "Int" + V "ArgString",
FieldName = V "Identifier" * (P "." + V "Identifier") ^ 1 * (P "[" * V "Index" * P "]") ^ -1,
Name = C(V "Identifier") * -V "idRest",
Hex = (P("0x") + P("0X")) * xdigit ^ 1,
Expo = S("eE") * S("+-") ^ -1 * digit ^ 1,
Float = (((digit ^ 1 * P(".") * digit ^ 0) + (P(".") * digit ^ 1)) * V "Expo" ^ -1) + (digit ^ 1 * V "Expo"),
Number = C(V "Hex" + V "Float" + V "Int") * - V "idStart" / function(n)
return tonumber(n)
end,
String = (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),
OrOp = kw("or") / "or",
AndOp = kw("and") / "and",
Colon = kw(":"),
RelOp = symb("=") / "=" + symb("==") / "==" + symb("!=") / "!=" + symb("<=") / "<=" + symb(">=") / ">=" +
symb("<") / "<" +
symb(">") / ">" +
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",
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.
--]]
function parser.parse_filter(subject)
local errorinfo = {subject = subject}
lpeg.setmaxstack(1000)
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
return ast, error_msg
end
function print_ast(ast, level)
local t = ast.type
level = level or 0
local prefix = string.rep(" ", level * 4)
level = level + 1
if t == "Rule" then
print_ast(ast.filter, level)
elseif t == "Filter" then
print_ast(ast.value, level)
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
print(prefix .. ast.operator)
print_ast(ast.left, level)
print_ast(ast.right, level)
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
print(prefix .. ast.operator)
print_ast(ast.argument, level)
elseif t == "List" then
for i, v in ipairs(ast.elements) do
print_ast(v, level)
end
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
print(prefix .. t .. " " .. ast.value)
elseif t == "MacroDef" then
-- don't print for now
else
error("Unexpected type in print_ast: " .. t)
end
end
parser.print_ast = print_ast
-- Traverse the provided ast and call the provided callback function
-- for any nodes of the specified type. The callback function should
-- have the signature:
-- cb(ast_node, ctx)
-- ctx is optional.
function traverse_ast(ast, node_types, cb, ctx)
local t = ast.type
if node_types[t] ~= nil then
cb(ast, ctx)
end
if t == "Rule" then
traverse_ast(ast.filter, node_types, cb, ctx)
elseif t == "Filter" then
traverse_ast(ast.value, node_types, cb, ctx)
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
traverse_ast(ast.left, node_types, cb, ctx)
traverse_ast(ast.right, node_types, cb, ctx)
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
traverse_ast(ast.argument, node_types, cb, ctx)
elseif t == "List" then
for i, v in ipairs(ast.elements) do
traverse_ast(v, node_types, cb, ctx)
end
elseif t == "MacroDef" then
traverse_ast(ast.value, node_types, cb, ctx)
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
-- do nothing, no traversal needed
else
error("Unexpected type in traverse_ast: " .. t)
end
end
parser.traverse_ast = traverse_ast
return parser

View File

@@ -0,0 +1,85 @@
# 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.
#!/bin/bash
function error_exit_good
{
echo "Error: '$1' did not compiler" 1>&2
exit 1
}
function error_exit_bad
{
echo "Error: incorrect filter '$1' compiler ok" 1>&2
exit 1
}
function good
{
lua5.1 test.lua "$1" 2> /dev/null || error_exit_good "$1"
}
function bad
{
lua5.1 test.lua "$1" 2> /dev/null && error_exit_bad "$1"
}
# Filters
good " a"
good "a and b"
good "#a and b; a and b"
good "#a and b; # ; ; a and b"
good "(a)"
good "(a and b)"
good "(a.a exists and b)"
good "(a.a exists) and (b)"
good "a.a exists and b"
good "a.a=1 or b.b=2 and c"
good "not (a)"
good "not (not (a))"
good "not (a.b=1)"
good "not (a.a exists)"
good "not a"
good "a.b = 1 and not a"
good "not not a"
good "(not not a)"
good "not a.b=1"
good "not a.a exists"
good "notz and a and b"
good "a.b = bla"
good "a.b = 'bla'"
good "a.b = not"
good "a.b contains bla"
good "a.b icontains 'bla'"
good "a.g in (1, 'a', b)"
good "a.g in ( 1 ,, , b)"
good "evt.dir=> and fd.name=*.log"
good "evt.dir=> and fd.name=/var/log/httpd.log"
good "a.g in (1, 'a', b.c)"
good "a.b = a.a"
good "evt.arg[0] contains /bin"
bad "evt.arg[a] contains /bin"
bad "evt.arg[] contains /bin"
bad "a.b = b = 1"
bad "(a.b = 1"
echo
echo "All tests passed."
exit 0

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2022 The Falco Authors.
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.
@@ -16,20 +16,7 @@ limitations under the License.
#pragma once
#include <map>
#include <string>
#include <vector>
#include "rule_loader.h"
#include "lua.h"
int luaopen_yaml (lua_State *L);
/*!
\brief Reads the contents of a ruleset
*/
class rule_reader
{
public:
/*!
\brief Reads the contents of a ruleset and uses a loader to store
thew new definitions
*/
virtual bool load(rule_loader::configuration& cfg, rule_loader& loader);
};

View File

@@ -1,777 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "falco_engine.h"
#include "rule_loader.h"
#include "filter_macro_resolver.h"
#include "filter_evttype_resolver.h"
#define MAX_VISIBILITY ((uint32_t) -1)
#define THROW(cond, err) { if (cond) { throw falco_exception(err); } }
static string s_container_info_fmt = "%container.info";
static string s_default_extra_fmt = "%container.name (id=%container.id)";
using namespace std;
using namespace libsinsp::filter;
// todo(jasondellaluce): this breaks string escaping in lists and exceptions
static void quote_item(string& e)
{
if (e.find(" ") != string::npos && e[0] != '"' && e[0] != '\'')
{
e = '"' + e + '"';
}
}
static void paren_item(string& e)
{
if(e[0] != '(')
{
e = '(' + e + ')';
}
}
static bool is_field_defined(
falco_engine *engine, const string& source, string field)
{
auto factory = engine->get_filter_factory(source);
if(factory)
{
auto *chk = factory->new_filtercheck(field.c_str());
if (chk)
{
delete(chk);
return true;
}
}
return false;
}
// todo(jasondellaluce): add an helper in libsinsp for this
static bool is_operator_defined(const string& op)
{
static vector<string> ops = {"=", "==", "!=", "<=", ">=", "<", ">",
"contains", "icontains", "bcontains", "glob", "bstartswith",
"startswith", "endswith", "in", "intersects", "pmatch"};
return find(ops.begin(), ops.end(), op) != ops.end();
}
// todo(jasondellaluce): add an helper in libsinsp for this
static bool is_operator_for_list(const string& op)
{
return op == "in" || op == "intersects" || op == "pmatch";
}
static bool is_format_valid(
falco_engine* e, const string& src, const string& fmt, string& err)
{
try
{
shared_ptr<gen_event_formatter> formatter;
formatter = e->create_formatter(src, fmt);
return true;
}
catch(exception &e)
{
err = e.what();
return false;
}
}
template <typename T>
static inline void define_info(indexed_vector<T>& infos, T& info, uint32_t id)
{
auto prev = infos.at(info.name);
if (prev)
{
info.index = prev->index;
info.visibility = id;
*prev = info;
}
else
{
info.index = id;
info.visibility = id;
infos.insert(info, info.name);
}
}
template <typename T>
static inline void append_info(T* prev, T& info, uint32_t id)
{
prev->visibility = id;
prev->ctx.append(info.ctx);
}
static void validate_exception_info(
rule_loader::configuration& cfg,
rule_loader::rule_exception_info &ex,
const string& source)
{
if (ex.fields.is_list)
{
if (!ex.comps.is_valid())
{
ex.comps.is_list = true;
for (size_t i = 0; i < ex.fields.items.size(); i++)
{
ex.comps.items.push_back({false, "="});
}
}
THROW(ex.fields.items.size() != ex.comps.items.size(),
"Rule exception item " + ex.name
+ ": fields and comps lists must have equal length");
for (auto &v : ex.comps.items)
{
THROW(!is_operator_defined(v.item),
"Rule exception item " + ex.name + ": comparison operator "
+ v.item + " is not a supported comparison operator");
}
for (auto &v : ex.fields.items)
{
THROW(!is_field_defined(cfg.engine, source, v.item),
"Rule exception item " + ex.name + ": field name "
+ v.item + " is not a supported filter field");
}
}
else
{
if (!ex.comps.is_valid())
{
ex.comps.is_list = false;
ex.comps.items.push_back({false, "in"});
}
THROW(ex.comps.is_list, "Rule exception item "
+ ex.name + ": fields and comps must both be strings");
THROW(!is_operator_defined(ex.comps.item),
"Rule exception item " + ex.name + ": comparison operator "
+ ex.comps.item + " is not a supported comparison operator");
THROW(!is_field_defined(cfg.engine, source, ex.fields.item),
"Rule exception item " + ex.name + ": field name "
+ ex.fields.item + " is not a supported filter field");
}
}
static void build_rule_exception_infos(
vector<rule_loader::rule_exception_info>& exceptions,
set<string>& exception_fields,
string& condition)
{
string tmp;
for (auto &ex : exceptions)
{
string icond;
if(!ex.fields.is_list)
{
for (auto &val : ex.values)
{
THROW(val.is_list, "Expected values array for item "
+ ex.name + " to contain a list of strings");
icond += icond.empty()
? ("(" + ex.fields.item + " "
+ ex.comps.item + " (")
: ", ";
exception_fields.insert(ex.fields.item);
tmp = val.item;
quote_item(tmp);
icond += tmp;
}
icond += icond.empty() ? "" : "))";
}
else
{
icond = "(";
for (auto &values : ex.values)
{
THROW(ex.fields.items.size() != values.items.size(),
"Exception item " + ex.name
+ ": fields and values lists must have equal length");
icond += icond == "(" ? "" : " or ";
icond += "(";
uint32_t k = 0;
string istr;
for (auto &field : ex.fields.items)
{
icond += k == 0 ? "" : " and ";
if (values.items[k].is_list)
{
istr = "(";
for (auto &v : values.items[k].items)
{
tmp = v.item;
quote_item(tmp);
istr += istr == "(" ? "" : ", ";
istr += tmp;
}
istr += ")";
}
else
{
istr = values.items[k].item;
if(is_operator_for_list(ex.comps.items[k].item))
{
paren_item(istr);
}
else
{
quote_item(istr);
}
}
icond += " " + field.item;
icond += " " + ex.comps.items[k].item + " " + istr;
exception_fields.insert(field.item);
k++;
}
icond += ")";
}
icond += ")";
if (icond == "()")
{
icond = "";
}
}
condition += icond.empty() ? "" : " and not " + icond;
}
}
// todo(jasondellaluce): this breaks string escaping in lists
static bool resolve_list(string& cnd, const rule_loader::list_info& list)
{
static string blanks = " \t\n\r";
static string delims = blanks + "(),=";
string new_cnd;
size_t start, end;
bool used = false;
start = cnd.find(list.name);
while (start != string::npos)
{
// the characters surrounding the name must
// be delims of beginning/end of string
end = start + list.name.length();
if ((start == 0 || delims.find(cnd[start - 1]) != string::npos)
&& (end >= cnd.length() || delims.find(cnd[end]) != string::npos))
{
// shift pointers to consume all whitespaces
while (start > 0
&& blanks.find(cnd[start - 1]) != string::npos)
{
start--;
}
while (end < cnd.length()
&& blanks.find(cnd[end]) != string::npos)
{
end++;
}
// create substitution string by concatenating all values
string sub = "";
for (auto &v : list.items)
{
if (!sub.empty())
{
sub += ", ";
}
sub += v;
}
// if substituted list is empty, we need to
// remove a comma from the left or the right
if (sub.empty())
{
if (start > 0 && cnd[start - 1] == ',')
{
start--;
}
else if (end < cnd.length() && cnd[end] == ',')
{
end++;
}
}
// compose new string with substitution
new_cnd = "";
if (start > 0)
{
new_cnd += cnd.substr(0, start) + " ";
}
new_cnd += sub + " ";
if (end <= cnd.length())
{
new_cnd += cnd.substr(end);
}
cnd = new_cnd;
start += sub.length() + 1;
used = true;
}
start = cnd.find(list.name, start + 1);
}
return used;
}
static void resolve_macros(
indexed_vector<rule_loader::macro_info>& macros,
shared_ptr<ast::expr>& ast,
uint32_t visibility,
const string& on_unknown_err_prefix)
{
filter_macro_resolver macro_resolver;
for (auto &m : macros)
{
if (m.index < visibility)
{
macro_resolver.set_macro(m.name, m.cond_ast);
}
}
macro_resolver.run(ast);
THROW(!macro_resolver.get_unknown_macros().empty(),
on_unknown_err_prefix + "Undefined macro '"
+ *macro_resolver.get_unknown_macros().begin()
+ "' used in filter.");
for (auto &m : macro_resolver.get_resolved_macros())
{
macros.at(m)->used = true;
}
}
// note: there is no visibility order between filter conditions and lists
static shared_ptr<ast::expr> parse_condition(
string condition,
indexed_vector<rule_loader::list_info>& lists)
{
for (auto &l : lists)
{
if (resolve_list(condition, l))
{
l.used = true;
}
}
libsinsp::filter::parser p(condition);
p.set_max_depth(1000);
try
{
shared_ptr<ast::expr> res_ptr(p.parse());
return res_ptr;
}
catch (const sinsp_exception& e)
{
throw falco_exception("Compilation error when compiling \""
+ condition + "\": " + to_string(p.get_pos().col) + ": " + e.what());
}
}
static shared_ptr<gen_event_filter> compile_condition(
falco_engine* engine,
uint32_t id,
shared_ptr<ast::expr> cnd,
string src,
string& err)
{
try
{
auto factory = engine->get_filter_factory(src);
sinsp_filter_compiler compiler(factory, cnd.get());
compiler.set_check_id(id);
shared_ptr<gen_event_filter> ret(compiler.compile());
return ret;
}
catch (const sinsp_exception& e)
{
err = e.what();
}
catch (const falco_exception& e)
{
err = e.what();
}
return nullptr;
}
static void apply_output_substitutions(
rule_loader::configuration& cfg,
string& out)
{
if (out.find(s_container_info_fmt) != string::npos)
{
if (cfg.replace_output_container_info)
{
out = replace(out, s_container_info_fmt, cfg.output_extra);
return;
}
out = replace(out, s_container_info_fmt, s_default_extra_fmt);
}
out += cfg.output_extra.empty() ? "" : " " + cfg.output_extra;
}
void rule_loader::clear()
{
m_cur_index = 0;
m_rule_infos.clear();
m_list_infos.clear();
m_macro_infos.clear();
m_required_plugin_versions.clear();
}
bool rule_loader::is_plugin_compatible(
const string &name,
const string &version,
string &required_version)
{
set<string> required_plugin_versions;
sinsp_plugin::version plugin_version(version);
if(!plugin_version.m_valid)
{
throw falco_exception(
string("Plugin version string ") + version + " not valid");
}
auto it = m_required_plugin_versions.find(name);
if (it != m_required_plugin_versions.end())
{
for (auto &rversion : it->second)
{
sinsp_plugin::version req_version(rversion);
if (!plugin_version.check(req_version))
{
required_version = rversion;
return false;
}
}
}
return true;
}
void rule_loader::define(configuration& cfg, engine_version_info& info)
{
auto v = falco_engine::engine_version();
THROW(v < info.version, "Rules require engine version "
+ to_string(info.version) + ", but engine version is " + to_string(v));
}
void rule_loader::define(configuration& cfg, plugin_version_info& info)
{
m_required_plugin_versions[info.name].insert(info.version);
}
void rule_loader::define(configuration& cfg, list_info& info)
{
define_info(m_list_infos, info, m_cur_index++);
}
void rule_loader::append(configuration& cfg, list_info& info)
{
auto prev = m_list_infos.at(info.name);
THROW(!prev, "List " + info.name +
" has 'append' key but no list by that name already exists");
prev->items.insert(prev->items.end(), info.items.begin(), info.items.end());
append_info(prev, info, m_cur_index++);
}
void rule_loader::define(configuration& cfg, macro_info& info)
{
if (!cfg.engine->is_source_valid(info.source))
{
cfg.warnings.push_back("Macro " + info.name
+ ": warning (unknown-source): unknown source "
+ info.source + ", skipping");
return;
}
define_info(m_macro_infos, info, m_cur_index++);
}
void rule_loader::append(configuration& cfg, macro_info& info)
{
auto prev = m_macro_infos.at(info.name);
THROW(!prev, "Macro " + info.name
+ " has 'append' key but no macro by that name already exists");
prev->cond += " ";
prev->cond += info.cond;
append_info(prev, info, m_cur_index++);
}
void rule_loader::define(configuration& cfg, rule_info& info)
{
if (!cfg.engine->is_source_valid(info.source))
{
cfg.warnings.push_back("Rule " + info.name
+ ": warning (unknown-source): unknown source "
+ info.source + ", skipping");
return;
}
auto prev = m_macro_infos.at(info.name);
THROW(prev && prev->source != info.source,
"Rule " + info.name + " has been re-defined with a different source");
for (auto &ex : info.exceptions)
{
THROW(!ex.fields.is_valid(), "Rule exception item "
+ ex.name + ": must have fields property with a list of fields");
validate_exception_info(cfg, ex, info.source);
}
define_info(m_rule_infos, info, m_cur_index++);
}
void rule_loader::append(configuration& cfg, rule_info& info)
{
auto prev = m_rule_infos.at(info.name);
THROW(!prev, "Rule " + info.name
+ " has 'append' key but no rule by that name already exists");
THROW(info.cond.empty() && info.exceptions.empty(),
"Appended rule must have exceptions or condition property");
if (!info.cond.empty())
{
prev->cond += " ";
prev->cond += info.cond;
}
for (auto &ex : info.exceptions)
{
auto prev_ex = find_if(prev->exceptions.begin(), prev->exceptions.end(),
[&ex](const rule_loader::rule_exception_info& i)
{ return i.name == ex.name; });
if (prev_ex == prev->exceptions.end())
{
THROW(!ex.fields.is_valid(), "Rule exception new item "
+ ex.name + ": must have fields property with a list of fields");
THROW(ex.values.empty(), "Rule exception new item "
+ ex.name + ": must have fields property with a list of values");
validate_exception_info(cfg, ex, prev->source);
prev->exceptions.push_back(ex);
}
else
{
THROW(ex.fields.is_valid(),
"Can not append exception fields to existing rule, only values");
THROW(ex.comps.is_valid(),
"Can not append exception comps to existing rule, only values");
prev_ex->values.insert(
prev_ex->values.end(), ex.values.begin(), ex.values.end());
}
}
append_info(prev, info, m_cur_index++);
}
void rule_loader::enable(configuration& cfg, rule_info& info)
{
auto prev = m_rule_infos.at(info.name);
THROW(!prev, "Rule " + info.name
+ " has 'enabled' key but no rule by that name already exists");
prev->enabled = info.enabled;
}
void rule_loader::compile_list_infos(configuration& cfg, indexed_vector<list_info>& out)
{
string tmp;
vector<string> used;
for (auto &list : m_list_infos)
{
try
{
list_info v = list;
v.items.clear();
for (auto &item : list.items)
{
auto ref = m_list_infos.at(item);
if (ref && ref->index < list.visibility)
{
used.push_back(ref->name);
for (auto val : ref->items)
{
quote_item(val);
v.items.push_back(val);
}
}
else
{
tmp = item;
quote_item(tmp);
v.items.push_back(tmp);
}
}
v.used = false;
out.insert(v, v.name);
}
catch (exception& e)
{
throw falco_exception(list.ctx.error(e.what()));
}
}
for (auto &v : used)
{
out.at(v)->used = true;
}
}
// note: there is a visibility ordering between macros
void rule_loader::compile_macros_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& out)
{
set<string> used;
context* info_ctx = NULL;
try
{
for (auto &m : m_macro_infos)
{
info_ctx = &m.ctx;
macro_info entry = m;
entry.cond_ast = parse_condition(m.cond, lists);
entry.used = false;
out.insert(entry, m.name);
}
for (auto &m : out)
{
info_ctx = &m.ctx;
resolve_macros(out, m.cond_ast, m.visibility,
"Compilation error when compiling \"" + m.cond + "\": ");
}
}
catch (exception& e)
{
throw falco_exception(info_ctx->error(e.what()));
}
}
void rule_loader::compile_rule_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out)
{
string err, condition;
for (auto &r : m_rule_infos)
{
try
{
if (r.priority > cfg.min_priority)
{
continue;
}
falco_rule rule;
condition = r.cond;
if (!r.exceptions.empty())
{
build_rule_exception_infos(
r.exceptions, rule.exception_fields, condition);
}
auto ast = parse_condition(condition, lists);
resolve_macros(macros, ast, MAX_VISIBILITY, "");
rule.output = r.output;
if (r.source == falco_common::syscall_source)
{
apply_output_substitutions(cfg, rule.output);
}
THROW(!is_format_valid(cfg.engine, r.source, rule.output, err),
"Invalid output format '" + rule.output + "': '" + err + "'");
rule.name = r.name;
rule.source = r.source;
rule.description = r.desc;
rule.priority = r.priority;
rule.tags = r.tags;
// note: indexes are 0-based, but 0 is not an acceptable rule_id
auto id = out.insert(rule, rule.name) + 1;
auto filter = compile_condition(cfg.engine, id, ast, rule.source, err);
if (!filter)
{
if (r.skip_if_unknown_filter
&& err.find("nonexistent field") != string::npos)
{
cfg.warnings.push_back(
"Rule " + rule.name + ": warning (unknown-field):");
continue;
}
else
{
throw falco_exception("Rule " + rule.name + ": error " + err);
}
}
set<uint16_t> evttypes;
if(rule.source == falco_common::syscall_source)
{
filter_evttype_resolver resolver;
resolver.evttypes(ast, evttypes);
if ((evttypes.empty() || evttypes.size() > 100)
&& r.warn_evttypes)
{
cfg.warnings.push_back(
"Rule " + rule.name + ": warning (no-evttype):\n" +
+ " matches too many evt.type values.\n"
+ " This has a significant performance penalty.");
}
}
else if (rule.source == "k8s_audit")
{
// todo(jasondellaluce): remove this case once k8saudit
// gets ported to a plugin
evttypes = { ppm_event_type::PPME_GENERIC_X };
}
else
{
evttypes = { ppm_event_type::PPME_PLUGINEVENT_E };
}
cfg.engine->add_filter(filter, rule.name, rule.source, evttypes, rule.tags);
cfg.engine->enable_rule(rule.name, r.enabled);
}
catch (exception& e)
{
throw falco_exception(r.ctx.error(e.what()));
}
}
}
bool rule_loader::compile(configuration& cfg, indexed_vector<falco_rule>& out)
{
indexed_vector<list_info> lists;
indexed_vector<macro_info> macros;
// expand all lists, macros, and rules
try
{
compile_list_infos(cfg, lists);
compile_macros_infos(cfg, lists, macros);
compile_rule_infos(cfg, lists, macros, out);
}
catch (exception& e)
{
cfg.errors.push_back(e.what());
return false;
}
// print info on any dangling lists or macros that were not used anywhere
for (auto &m : macros)
{
if (!m.used)
{
cfg.warnings.push_back("macro " + m.name
+ " not referred to by any rule/macro");
}
}
for (auto &l : lists)
{
if (!l.used)
{
cfg.warnings.push_back("list " + l.name
+ " not referred to by any rule/macro/list");
}
}
return true;
}

View File

@@ -1,239 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <map>
#include <string>
#include <vector>
#include <yaml-cpp/yaml.h>
#include "falco_rule.h"
#include "indexed_vector.h"
// todo(jasondellaluce): remove this cyclic dependency
class falco_engine;
/*!
\brief Ruleset loader of the falco engine
*/
class rule_loader
{
public:
/*!
\brief Represents a section of text from which a certain info
struct has been decoded
*/
struct context
{
std::string content;
/*!
\brief Wraps an error by adding info about the text section
*/
inline std::string error(std::string err)
{
err += "\n---\n";
err += trim(content);
err += "\n---";
return err;
}
/*!
\brief Appends another text section info to this one
*/
inline void append(context& m)
{
content += "\n\n";
content += m.content;
}
};
/*!
\brief Contains the info required to load rule definitions
*/
struct configuration
{
configuration(const std::string& cont): content(cont) {}
const std::string& content;
std::string output_extra;
bool replace_output_container_info;
falco_common::priority_type min_priority;
std::vector<std::string> warnings;
std::vector<std::string> errors;
falco_engine* engine;
};
/*!
\brief Represents infos about an engine version requirement
*/
struct engine_version_info
{
uint32_t version;
};
/*!
\brief Represents infos about a plugin version requirement
*/
struct plugin_version_info
{
std::string name;
std::string version;
};
/*!
\brief Represents infos about a list
*/
struct list_info
{
context ctx;
bool used;
size_t index;
size_t visibility;
std::string name;
std::vector<std::string> items;
};
/*!
\brief Represents infos about a macro
*/
struct macro_info
{
context ctx;
bool used;
size_t index;
size_t visibility;
std::string name;
std::string cond;
std::string source;
std::shared_ptr<libsinsp::filter::ast::expr> cond_ast;
};
/*!
\brief Represents infos about a single rule exception
*/
struct rule_exception_info
{
/*!
\brief This is necessary due to the dynamic-typed nature of
exceptions. Each of fields, comps, and values, can either be a
single value or a list of values. This is a simple hack to make
this easier to implement in C++, that is not non-dynamic-typed.
*/
struct entry {
bool is_list;
std::string item;
std::vector<entry> items;
inline bool is_valid() const
{
return (is_list && !items.empty())
|| (!is_list && !item.empty());
}
};
std::string name;
entry fields;
entry comps;
std::vector<entry> values;
};
/*!
\brief Represents infos about a rule
*/
struct rule_info
{
context ctx;
size_t index;
size_t visibility;
std::string name;
std::string cond;
std::string source;
std::string desc;
std::string output;
std::set<std::string> tags;
std::vector<rule_exception_info> exceptions;
falco_common::priority_type priority;
bool enabled;
bool warn_evttypes;
bool skip_if_unknown_filter;
};
/*!
\brief Erases all the internal state and definitions
*/
virtual void clear();
/*!
\brief Returns true if the given plugin name and version are compatible
with the internal definitions. If false is returned, required_version is
filled with the required plugin version that didn't match.
*/
virtual bool is_plugin_compatible(
const std::string& name,
const std::string& version,
std::string& required_version);
/*!
\brief Uses the internal state to compile a list of falco_rules
*/
bool compile(configuration& cfg, indexed_vector<falco_rule>& out);
/*!
\brief Defines an info block. If a similar info block is found
in the internal state (e.g. another rule with same name), then
the previous definition gets overwritten
*/
virtual void define(configuration& cfg, engine_version_info& info);
virtual void define(configuration& cfg, plugin_version_info& info);
virtual void define(configuration& cfg, list_info& info);
virtual void define(configuration& cfg, macro_info& info);
virtual void define(configuration& cfg, rule_info& info);
/*!
\brief Appends an info block to an existing one. An exception
is thrown if no existing definition can be matched with the appended
one
*/
virtual void append(configuration& cfg, list_info& info);
virtual void append(configuration& cfg, macro_info& info);
virtual void append(configuration& cfg, rule_info& info);
/*!
\brief Updates the 'enabled' flag of an existing definition
*/
virtual void enable(configuration& cfg, rule_info& info);
private:
void compile_list_infos(
configuration& cfg,
indexed_vector<list_info>& out);
void compile_macros_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& out);
void compile_rule_infos(
configuration& cfg,
indexed_vector<list_info>& lists,
indexed_vector<macro_info>& macros,
indexed_vector<falco_rule>& out);
uint32_t m_cur_index;
indexed_vector<rule_info> m_rule_infos;
indexed_vector<macro_info> m_macro_infos;
indexed_vector<list_info> m_list_infos;
std::map<std::string, std::set<std::string>> m_required_plugin_versions;
};

View File

@@ -1,325 +0,0 @@
/*
Copyright (C) 2022 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "rule_reader.h"
#define THROW(cond, err) { if (cond) { throw falco_exception(err); } }
static rule_loader::context yaml_get_context(
const string& content,
const vector<YAML::Node>& docs,
vector<YAML::Node>::iterator doc,
YAML::iterator node)
{
rule_loader::context m;
YAML::Node item = *node++;
YAML::Node cur_doc = *doc++;
// include the "- " sequence mark
size_t from = item.Mark().pos - 2;
size_t to = 0;
if (node != cur_doc.end())
{
// end of item is beginning of next item
to = node->Mark().pos - 2;
}
else if (doc != docs.end())
{
// end of item is beginning of next doc
to = doc->Mark().pos - 4;
}
else
{
// end of item is end of file contents
to = content.length();
}
m.content = content.substr(from, to - from);
m.content = trim(m.content);
return m;
}
template <typename T>
static bool decode_val(const YAML::Node& v, T& out)
{
return v.IsDefined() && v.IsScalar() && YAML::convert<T>::decode(v, out);
}
template <typename T>
static bool decode_seq(const YAML::Node& item, vector<T>& out)
{
if (item.IsDefined() && item.IsSequence())
{
T value;
for(const YAML::Node& v : item)
{
THROW(!v.IsScalar() || !YAML::convert<T>::decode(v, value),
"Can't decode YAML sequence value: " + YAML::Dump(v));
out.push_back(value);
}
return true;
}
return false;
}
template <typename T>
static bool decode_seq(const YAML::Node& item, set<T>& out)
{
if (item.IsDefined() && item.IsSequence())
{
T value;
for(const YAML::Node& v : item)
{
THROW(!v.IsScalar() || !YAML::convert<T>::decode(v, value),
"Can't decode YAML sequence value: " + YAML::Dump(v));
out.insert(value);
}
return true;
}
return false;
}
static bool decode_exception_info_entry(
const YAML::Node& item,
rule_loader::rule_exception_info::entry& out)
{
if (item.IsDefined())
{
if (item.IsScalar())
{
out.is_list = false;
if (YAML::convert<string>::decode(item, out.item))
{
return true;
}
}
if (item.IsSequence())
{
out.is_list = true;
rule_loader::rule_exception_info::entry tmp;
for(const YAML::Node& v : item)
{
if (!decode_exception_info_entry(v, tmp))
{
return false;
}
out.items.push_back(tmp);
}
return true;
}
}
return false;
}
static void read_rule_exceptions(
const YAML::Node& item,
rule_loader::rule_info& v)
{
THROW(!item.IsSequence(), "Rule exceptions must be a sequence");
for (auto &ex : item)
{
rule_loader::rule_exception_info v_ex;
THROW(!decode_val(ex["name"], v_ex.name) || v_ex.name.empty(),
"Rule exception item must have name property");
// note: the legacy lua loader used to throw a "xxx must strings" error
decode_exception_info_entry(ex["fields"], v_ex.fields);
decode_exception_info_entry(ex["comps"], v_ex.comps);
if (ex["values"].IsDefined())
{
THROW(!ex["values"].IsSequence(),
"Rule exception values must be a sequence");
for (auto &val : ex["values"])
{
rule_loader::rule_exception_info::entry v_ex_val;
decode_exception_info_entry(val, v_ex_val);
v_ex.values.push_back(v_ex_val);
}
}
v.exceptions.push_back(v_ex);
}
}
static void read_item(
rule_loader::configuration& cfg,
rule_loader& loader,
const YAML::Node& item,
const rule_loader::context& ctx)
{
if (item["required_engine_version"].IsDefined())
{
rule_loader::engine_version_info v;
THROW(!decode_val(item["required_engine_version"], v.version),
"Value of required_engine_version must be a number");
loader.define(cfg, v);
}
else if(item["required_plugin_versions"].IsDefined())
{
THROW(!item["required_plugin_versions"].IsSequence(),
"Value of required_plugin_versions must be a sequence");
for(const YAML::Node& plugin : item["required_plugin_versions"])
{
rule_loader::plugin_version_info v;
THROW(!decode_val(plugin["name"], v.name) || v.name.empty(),
"required_plugin_versions item must have name property");
THROW(!decode_val(plugin["version"], v.version) || v.version.empty(),
"required_plugin_versions item must have version property");
loader.define(cfg, v);
}
}
else if(item["list"].IsDefined())
{
rule_loader::list_info v;
v.ctx = ctx;
bool append = false;
THROW(!decode_val(item["list"], v.name) || v.name.empty(),
"List name is empty");
THROW(!decode_seq(item["items"], v.items),
"List must have property items");
if(decode_val(item["append"], append) && append)
{
loader.append(cfg, v);
}
else
{
loader.define(cfg, v);
}
}
else if(item["macro"].IsDefined())
{
rule_loader::macro_info v;
v.ctx = ctx;
bool append = false;
v.source = falco_common::syscall_source;
THROW(!decode_val(item["macro"], v.name) || v.name.empty(),
"Macro name is empty");
THROW(!decode_val(item["condition"], v.cond) || v.cond.empty(),
"Macro must have property condition");
decode_val(item["source"], v.source);
if(decode_val(item["append"], append) && append)
{
loader.append(cfg, v);
}
else
{
loader.define(cfg, v);
}
}
else if(item["rule"].IsDefined())
{
rule_loader::rule_info v;
v.ctx = ctx;
bool append = false;
v.enabled = true;
v.warn_evttypes = true;
v.skip_if_unknown_filter = false;
THROW(!decode_val(item["rule"], v.name) || v.name.empty(),
"Rule name is empty");
if(decode_val(item["append"], append) && append)
{
decode_val(item["condition"], v.cond);
if (item["exceptions"].IsDefined())
{
read_rule_exceptions(item["exceptions"], v);
}
loader.append(cfg, v);
}
else
{
string priority;
bool has_enabled = decode_val(item["enabled"], v.enabled);
bool has_defs = decode_val(item["condition"], v.cond)
&& decode_val(item["output"], v.output)
&& decode_val(item["desc"], v.desc)
&& decode_val(item["priority"], priority);
if (!has_defs)
{
THROW(!has_enabled, "Rule must have properties 'condition', 'output', 'desc', and 'priority'");
loader.enable(cfg, v);
}
else
{
v.output = trim(v.output);
v.source = falco_common::syscall_source;
THROW(!falco_common::parse_priority(priority, v.priority),
"Invalid priority");
decode_val(item["source"], v.source);
decode_val(item["warn_evttypes"], v.warn_evttypes);
decode_val(item["skip-if-unknown-filter"], v.skip_if_unknown_filter);
decode_seq(item["tags"], v.tags);
if (item["exceptions"].IsDefined())
{
read_rule_exceptions(item["exceptions"], v);
}
loader.define(cfg, v);
}
}
}
else
{
cfg.warnings.push_back("Unknown top level object");
}
}
bool rule_reader::load(rule_loader::configuration& cfg, rule_loader& loader)
{
std::vector<YAML::Node> docs;
try
{
docs = YAML::LoadAll(cfg.content);
}
catch(const exception& e)
{
cfg.errors.push_back("Could not load YAML file: " + string(e.what()));
return false;
}
for (auto doc = docs.begin(); doc != docs.end(); doc++)
{
if (doc->IsDefined() && !doc->IsNull())
{
if(!doc->IsMap() && !doc->IsSequence())
{
cfg.errors.push_back("Rules content is not yaml");
return false;
}
if(!doc->IsSequence())
{
cfg.errors.push_back(
"Rules content is not yaml array of objects");
return false;
}
for (auto it = doc->begin(); it != doc->end(); it++)
{
if (!it->IsNull())
{
auto ctx = yaml_get_context(cfg.content, docs, doc, it);
YAML::Node item = *it;
try
{
THROW(!item.IsMap(), "Unexpected element type. "
"Each element should be a yaml associative array.");
read_item(cfg, loader, item, ctx);
}
catch(const exception& e)
{
cfg.errors.push_back(ctx.error(e.what()));
return false;
}
}
}
}
}
return true;
}

495
userspace/engine/rules.cpp Normal file
View File

@@ -0,0 +1,495 @@
/*
Copyright (C) 2019 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <sstream>
#include "rules.h"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include "falco_engine.h"
#include "banned.h" // This raises a compilation error when certain functions are used
const static struct luaL_Reg ll_falco_rules[] =
{
{"clear_filters", &falco_rules::clear_filters},
{"create_lua_parser", &falco_rules::create_lua_parser},
{"add_filter", &falco_rules::add_filter},
{"enable_rule", &falco_rules::enable_rule},
{"engine_version", &falco_rules::engine_version},
{"is_source_valid", &falco_rules::is_source_valid},
{"is_format_valid", &falco_rules::is_format_valid},
{"is_defined_field", &falco_rules::is_defined_field},
{NULL, NULL}};
falco_rules::falco_rules(falco_engine *engine,
lua_State *ls)
: m_engine(engine),
m_ls(ls)
{
}
void falco_rules::add_filter_factory(const std::string &source,
std::shared_ptr<gen_event_filter_factory> factory)
{
m_filter_factories[source] = factory;
}
void falco_rules::init(lua_State *ls)
{
luaL_openlib(ls, "falco_rules", ll_falco_rules, 0);
lua_parser::register_callbacks(ls, "filter");
}
int falco_rules::clear_filters(lua_State *ls)
{
if (! lua_islightuserdata(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to clear_filters()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -1);
rules->clear_filters();
return 0;
}
void falco_rules::clear_filters()
{
m_engine->clear_filters();
}
int falco_rules::create_lua_parser(lua_State *ls)
{
if (! lua_islightuserdata(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid argument passed to create_lua_parser()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
std::string source = lua_tostring(ls, -1);
std::string errstr;
lua_parser *lp = rules->create_lua_parser(source, errstr);
if(lp == NULL) {
lua_pushstring(ls, errstr.c_str());
lua_error(ls);
}
lua_pushlightuserdata(ls, lp);
return 1;
}
lua_parser *falco_rules::create_lua_parser(std::string &source, std::string &errstr)
{
auto it = m_filter_factories.find(source);
if(it == m_filter_factories.end())
{
errstr = string("Unknown event source ") + source;
return NULL;
}
lua_parser *lp = new lua_parser(it->second);
return lp;
}
int falco_rules::add_filter(lua_State *ls)
{
if (! lua_islightuserdata(ls, -5) ||
! lua_islightuserdata(ls, -4) ||
! lua_isstring(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_istable(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to add_filter()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -5);
lua_parser *lp = (lua_parser *) lua_topointer(ls, -4);
std::string rule = lua_tostring(ls, -3);
std::string source = lua_tostring(ls, -2);
set<string> tags;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -2) != 0) {
// key is at index -2, value is at index
// -1. We want the values.
tags.insert(lua_tostring(ls, -1));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
size_t num_evttypes = lp->filter()->evttypes().size();
try {
rules->add_filter(lp->filter(), rule, source, tags);
} catch (exception &e) {
std::string errstr = string("Could not add rule to falco engine: ") + e.what();
lua_pushstring(ls, errstr.c_str());
lua_error(ls);
}
delete lp;
lua_pushnumber(ls, num_evttypes);
return 1;
}
void falco_rules::add_filter(std::shared_ptr<gen_event_filter> filter, string &rule, string &source, set<string> &tags)
{
m_engine->add_filter(filter, rule, source, tags);
}
int falco_rules::enable_rule(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isnumber(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to enable_rule()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
const char *rulec = lua_tostring(ls, -2);
std::string rule = rulec;
bool enabled = (lua_tonumber(ls, -1) ? true : false);
rules->enable_rule(rule, enabled);
return 0;
}
void falco_rules::enable_rule(string &rule, bool enabled)
{
m_engine->enable_rule(rule, enabled);
}
int falco_rules::engine_version(lua_State *ls)
{
if (! lua_islightuserdata(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to engine_version()");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -1);
lua_pushnumber(ls, rules->m_engine->engine_version());
return 1;
}
bool falco_rules::is_source_valid(const std::string &source)
{
return m_engine->is_source_valid(source);
}
int falco_rules::is_source_valid(lua_State *ls)
{
if (! lua_islightuserdata(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_source_valid");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
string source = luaL_checkstring(ls, -1);
bool ret = rules->is_source_valid(source);
lua_pushboolean(ls, (ret ? 1 : 0));
return 1;
}
int falco_rules::is_format_valid(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_format_valid");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
string source = luaL_checkstring(ls, -2);
string format = luaL_checkstring(ls, -1);
string errstr;
bool ret = rules->is_format_valid(source, format, errstr);
if (!ret)
{
lua_pushstring(ls, errstr.c_str());
}
else
{
lua_pushnil(ls);
}
return 1;
}
bool falco_rules::is_format_valid(const std::string &source, const std::string &format, std::string &errstr)
{
bool ret = true;
try
{
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_engine->create_formatter(source, format);
}
catch(exception &e)
{
std::ostringstream os;
os << "Invalid output format '"
<< format
<< "': '"
<< e.what()
<< "'";
errstr = os.str();
ret = false;
}
return ret;
}
int falco_rules::is_defined_field(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_isstring(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to is_defined_field");
lua_error(ls);
}
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
string source = luaL_checkstring(ls, -2);
string fldname = luaL_checkstring(ls, -1);
bool ret = rules->is_defined_field(source, fldname);
lua_pushboolean(ls, (ret ? 1 : 0));
return 1;
}
bool falco_rules::is_defined_field(const std::string &source, const std::string &fldname)
{
auto it = m_filter_factories.find(source);
if(it == m_filter_factories.end())
{
return false;
}
auto *chk = it->second->new_filtercheck(fldname.c_str());
if (chk == NULL)
{
return false;
}
delete(chk);
return true;
}
static std::list<std::string> get_lua_table_values(lua_State *ls, int idx)
{
std::list<std::string> ret;
if (lua_isnil(ls, idx)) {
return ret;
}
lua_pushnil(ls); /* first key */
while (lua_next(ls, idx-1) != 0) {
// key is at index -2, value is at index
// -1. We want the values.
if (! lua_isstring(ls, -1)) {
std::string err = "Non-string value in table of strings";
throw falco_exception(err);
}
ret.push_back(string(lua_tostring(ls, -1)));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
return ret;
}
static void get_lua_table_list_values(lua_State *ls,
int idx,
std::map<std::string, std::list<std::string>> &required_plugin_versions)
{
if (lua_isnil(ls, idx)) {
return;
}
lua_pushnil(ls); /* first key */
while (lua_next(ls, idx-1) != 0) {
// key is at index -2, table of values is at index -1.
if (! lua_isstring(ls, -2)) {
std::string err = "Non-string key in table of strings";
throw falco_exception(err);
}
std::string key = string(lua_tostring(ls, -2));
std::list<std::string> vals = get_lua_table_values(ls, -1);
if (required_plugin_versions.find(key) == required_plugin_versions.end())
{
required_plugin_versions[key] = vals;
}
else
{
required_plugin_versions[key].insert(required_plugin_versions[key].end(),
vals.begin(),
vals.end());
}
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
}
void falco_rules::load_rules(const string &rules_content,
bool verbose, bool all_events,
string &extra, bool replace_container_info,
falco_common::priority_type min_priority,
uint64_t &required_engine_version,
std::map<std::string, std::list<std::string>> &required_plugin_versions)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
{
lua_pushstring(m_ls, rules_content.c_str());
lua_pushlightuserdata(m_ls, this);
lua_pushboolean(m_ls, (verbose ? 1 : 0));
lua_pushboolean(m_ls, (all_events ? 1 : 0));
lua_pushstring(m_ls, extra.c_str());
lua_pushboolean(m_ls, (replace_container_info ? 1 : 0));
lua_pushnumber(m_ls, min_priority);
if(lua_pcall(m_ls, 7, 5, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error loading rules: " + string(lerr);
throw falco_exception(err);
}
// Returns:
// Load result: bool
// required engine version: will be nil when load result is false
// required_plugin_versions: will be nil when load result is false
// array of errors
// array of warnings
bool successful = lua_toboolean(m_ls, -5);
required_engine_version = lua_tonumber(m_ls, -4);
get_lua_table_list_values(m_ls, -3, required_plugin_versions);
std::list<std::string> errors = get_lua_table_values(m_ls, -2);
std::list<std::string> warnings = get_lua_table_values(m_ls, -1);
// Concatenate errors/warnings
std::ostringstream os;
if (errors.size() > 0)
{
os << errors.size() << " errors:" << std::endl;
for(auto err : errors)
{
os << err << std::endl;
}
}
if (warnings.size() > 0)
{
os << warnings.size() << " warnings:" << std::endl;
for(auto warn : warnings)
{
os << warn << std::endl;
}
}
if(!successful)
{
throw falco_exception(os.str());
}
if (verbose && os.str() != "") {
// We don't really have a logging callback
// from the falco engine, but this would be a
// good place to use it.
fprintf(stderr, "When reading rules content: %s", os.str().c_str());
}
lua_pop(m_ls, 4);
} else {
throw falco_exception("No function " + m_lua_load_rules + " found in lua rule module");
}
}
void falco_rules::describe_rule(std::string *rule)
{
lua_getglobal(m_ls, m_lua_describe_rule.c_str());
if(lua_isfunction(m_ls, -1))
{
if (rule == NULL)
{
lua_pushnil(m_ls);
} else {
lua_pushstring(m_ls, rule->c_str());
}
if(lua_pcall(m_ls, 1, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Could not describe " + (rule == NULL ? "all rules" : "rule " + *rule) + ": " + string(lerr);
throw falco_exception(err);
}
} else {
throw falco_exception("No function " + m_lua_describe_rule + " found in lua rule module");
}
}
falco_rules::~falco_rules()
{
}

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