Compare commits

..

6 Commits

Author SHA1 Message Date
Mark Stemm
0c3fe8a4e7 This version works
Minimally working version that can link a go program against a so with
the embedded falco engine. Running the program opens the engine for
syscalls and prints any output strings on alert.

It assumes the device already exists and the kernel module is
loaded. Also assumes the lua code is below /user/share--we'll want to
bake that into the shared library.

Lots of memory leaks still, the interface from go to c is still
monolithic, and I had to change the config of openssl crypto and
luajit to compile with -fPIC in order to link into the shared library,
but this version shows its feasible.
2021-08-11 13:16:29 -07:00
Mark Stemm
5787dfa098 Update libs version 2021-08-11 13:15:37 -07:00
Mark Stemm
2434942bdc Example program using embed engine
Not working yet
2021-08-10 16:59:29 -07:00
Mark Stemm
2f7b72d670 Add to cmake 2021-08-10 16:59:06 -07:00
Mark Stemm
50e8da1049 Fix compile errors 2021-08-10 16:57:44 -07:00
Mark Stemm
5c398bd396 Embeddable falco engine experiments
Still WIP/Experimental
2021-08-09 14:51:33 -05:00
120 changed files with 3882 additions and 4819 deletions

View File

@@ -292,7 +292,6 @@ jobs:
BUILD_DIR: "/build-static"
BUILD_TYPE: "release"
SKIP_PACKAGES_TESTS: "true"
SKIP_PLUGINS_TESTS: "true"
steps:
- setup_remote_docker
- attach_workspace:
@@ -304,7 +303,7 @@ jobs:
path: /build-static/release/integration-tests-xunit
"tests/driver-loader/integration":
machine:
image: ubuntu-2004:202107-02
image: ubuntu-1604:202004-01
steps:
- attach_workspace:
at: /tmp/ws
@@ -391,15 +390,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
@@ -410,7 +403,7 @@ jobs:
name: Setup
command: |
apt update -y
apt-get install apt-utils bzip2 gpg python python3-pip -y
apt-get install apt-utils bzip2 gpg python python-pip -y
pip install awscli
echo $GPG_KEY | base64 -d | gpg --import
- run:
@@ -511,15 +504,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/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
@@ -530,7 +517,7 @@ jobs:
name: Setup
command: |
apt update -y
apt-get install apt-utils bzip2 gpg python python3-pip -y
apt-get install apt-utils bzip2 gpg python python-pip -y
pip install awscli
echo $GPG_KEY | base64 -d | gpg --import
- run:
@@ -723,6 +710,7 @@ workflows:
- falco
- test-infra
requires:
- "build/musl"
- "rpm/sign"
filters:
tags:

5
.gitignore vendored
View File

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

View File

@@ -5,7 +5,7 @@ Known end users with notable contributions to the project include:
* IBM
* Red Hat
Falco is being used by numerous other companies, both large and small, to build higher layer products and services. The list includes but is not limited to:
Falco is being used by numerous other companies, both large and small, to build higher layer products and services. The list includes but is not limited to:
* Equinix Metal
* IEEE
* Lowes
@@ -26,8 +26,6 @@ This is a list of production adopters of Falco (in alphabetical order):
* [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.
* [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.
@@ -42,13 +40,9 @@ This is a list of production adopters of Falco (in alphabetical order):
* [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 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.
* [Replicated](https://www.replicated.com/) - Replicated is the modern way to ship on-prem software. Replicated gives software vendors a container-based platform for easily deploying cloud native applications inside customers' environments to provide greater security and control. Replicated uses Falco as runtime security to detect threats in the Kubernetes clusters which host our critical SaaS services.
* [Secureworks](https://www.secureworks.com/) - Secureworks is a leading worldwide cybersecurity company with a cloud-native security product that combines the power of human intellect with security analytics to unify detection and response across cloud, network, and endpoint environments for improved security operations and outcomes. Our Taegis XDR platform and detection system processes petabytes of security relevant data to expose active threats amongst the billions of daily events from our customers. We are proud to protect our platforms Kubernetes deployments, as well as help our customers protect their own Linux and container environments, using Falco.
* [Secureworks](https://www.secureworks.com/) - Secureworks is a leading worldwide cybersecurity company with a cloud-native security product that combines the power of human intellect with security analytics to unify detection and response across cloud, network, and endpoint environments for improved security operations and outcomes. Our Taegis XDR platform and detection system processes petabytes of security relevant data to expose active threats amongst the billions of daily events from our customers. We are proud to protect our platforms Kubernetes deployments, as well as help our customers protect their own Linux and container environments, using Falco.
* [Shopify](https://www.shopify.com) - Shopify is the leading multi-channel commerce platform. Merchants use Shopify to design, set up, and manage their stores across multiple sales channels, including mobile, web, social media, marketplaces, brick-and-mortar locations, and pop-up shops. The platform also provides merchants with a powerful back-office and a single view of their business, from payments to shipping. The Shopify platform was engineered for reliability and scale, making enterprise-level technology available to businesses of all sizes. Shopify uses Falco to complement its Host and Network Intrusion Detection Systems.
@@ -60,18 +54,12 @@ This is a list of production adopters of Falco (in alphabetical order):
* [Swissblock Technologies](https://swissblock.net/) At Swissblock we connect the dots by combining cutting-edge algorithmic trading strategies with in-depth market analysis. We route all Falco events to our control systems, both monitoring and logging. Being able to deeply analyse alerts, we can understand what is running on our Kubernetes clusters and check against security policies, specifically defined for each workload. A set of alarms notifies us in case of critical events, letting us react fast. In the near future we plan to build a little application to route Kubernetes internal events directly to Falco, fully leveraging Falco PodSecurityPolicies analyses.
* [Shapesecurity/F5](https://www.shapesecurity.com/) Shapesecurity defends against application fraud attacks like Account Take Over, Credential Stuffing, Fake Accounts, etc. Required by FedRamp certification, we needed to find a FIM solution to help monitor and protect our Kubernetes clusters. Traditional FIM solutions were not scalable and not working for our environment, but with Falco we found the solution we needed. Falco's detection capabilities have helped us identify anomalous behaviour within our clusters. We leverage Sidekick (https://github.com/falcosecurity/charts/tree/master/falcosidekick) to send Falco alerts to a PubSub which in turn publishes those alerts to our SIEM (SumoLogic)
* [Shapesecurity/F5] (https://www.shapesecurity.com/) Shapesecurity defends against application fraud attacks like Account Take Over, Credential Stuffing, Fake Accounts, etc. Required by FedRamp certification, we needed to find a FIM solution to help monitor and protect our Kubernetes clusters. Traditional FIM solutions were not scalable and not working for our environment, but with Falco we found the solution we needed. Falco's detection capabilities have helped us identify anomalous behaviour within our clusters. We leverage Sidekick (https://github.com/falcosecurity/charts/tree/master/falcosidekick) to send Falco alerts to a PubSub which in turn publishes those alerts to our SIEM (SumoLogic)
* [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 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
* [R6/Phoenix](https://r6security.com/) is an attack surface protection company that uses moving target defense to provide fully automated, proactive and devops friendly security to its customers. There are a set of policies you can add to enable the moving target defense capabilities. Some of them are triggered by a combination of Falco's findings. You can kill, restart and rename pods according to the ever changing policies.
* [SysFlow](https://sysflow.io) SysFlow is a cloud-native system telemetry framework that focuses on data abstraction, behavioral analytics, and noise reduction. At its core, SysFlow exposes a compact open telemetry format that records workload behaviors by connecting event and flow representations of process control flows, file interactions, and network communications. The resulting abstraction encodes a graph structure that enables provenance reasoning on host and container environments, and fast retrieval of security-relevant information.
## Adding a name
If you would like to add your name to this file, submit a pull request with your change.
If you would like to add your name to this file, submit a pull request with your change.

View File

@@ -1,47 +1,5 @@
# Change Log
## v0.30.0
Released on 2021-10-01
### Major Changes
* new: add `--k8s-node` command-line options, which allows filtering by a node when requesting metadata of pods to the K8s API server [[#1671](https://github.com/falcosecurity/falco/pull/1671)] - [@leogr](https://github.com/leogr)
* new(outputs): expose rule tags and event source in gRPC and json outputs [[#1714](https://github.com/falcosecurity/falco/pull/1714)] - [@jasondellaluce](https://github.com/jasondellaluce)
* new(userspace/falco): add customizable metadata fetching params [[#1667](https://github.com/falcosecurity/falco/pull/1667)] - [@zuc](https://github.com/zuc)
### Minor Changes
* update: bump driver version to 3aa7a83bf7b9e6229a3824e3fd1f4452d1e95cb4 [[#1744](https://github.com/falcosecurity/falco/pull/1744)] - [@zuc](https://github.com/zuc)
* docs: clarify that previous Falco drivers will remain available at https://download.falco.org and no automated cleanup is run anymore [[#1738](https://github.com/falcosecurity/falco/pull/1738)] - [@leodido](https://github.com/leodido)
* update(outputs): add configuration option for tags in json outputs [[#1733](https://github.com/falcosecurity/falco/pull/1733)] - [@jasondellaluce](https://github.com/jasondellaluce)
### Bug Fixes
* fix(scripts): correct standard output redirection in systemd config (DEB and RPM packages) [[#1697](https://github.com/falcosecurity/falco/pull/1697)] - [@chirabino](https://github.com/chirabino)
* fix(scripts): correct lookup order when trying multiple `gcc` versions in the `falco-driver-loader` script [[#1716](https://github.com/falcosecurity/falco/pull/1716)] - [@Spartan-65](https://github.com/Spartan-65)
### Rule Changes
* rule(list miner_domains): add new miner domains [[#1729](https://github.com/falcosecurity/falco/pull/1729)] - [@AlbertoPellitteri](https://github.com/AlbertoPellitteri)
* rule(list https_miner_domains): add new miner domains [[#1729](https://github.com/falcosecurity/falco/pull/1729)] - [@AlbertoPellitteri](https://github.com/AlbertoPellitteri)
### Non user-facing changes
* add Qonto as adopter [[#1717](https://github.com/falcosecurity/falco/pull/1717)] - [@Issif](https://github.com/Issif)
* docs(proposals): proposal for a libs plugin system [[#1637](https://github.com/falcosecurity/falco/pull/1637)] - [@ldegio](https://github.com/ldegio)
* build: remove unused `ncurses` dependency [[#1658](https://github.com/falcosecurity/falco/pull/1658)] - [@leogr](https://github.com/leogr)
* build(.circleci): use new Debian 11 package names for python-pip [[#1712](https://github.com/falcosecurity/falco/pull/1712)] - [@zuc](https://github.com/zuc)
* build(docker): adding libssl-dev, upstream image reference pinned to `debian:buster` [[#1719](https://github.com/falcosecurity/falco/pull/1719)] - [@michalschott](https://github.com/michalschott)
* fix(test): avoid output_strictly_contains failures [[#1724](https://github.com/falcosecurity/falco/pull/1724)] - [@jasondellaluce](https://github.com/jasondellaluce)
* Remove duplicate allowed ecr registry rule [[#1725](https://github.com/falcosecurity/falco/pull/1725)] - [@TomKeyte](https://github.com/TomKeyte)
* docs(RELEASE.md): switch to 3 releases per year [[#1711](https://github.com/falcosecurity/falco/pull/1711)] - [@leogr](https://github.com/leogr)
## v0.29.1
Released on 2021-06-29

View File

@@ -67,7 +67,6 @@ endif()
if(MUSL_OPTIMIZED_BUILD)
set(MUSL_FLAGS "-static -Os -fPIE -pie")
add_definitions(-DMUSL_OPTIMIZED)
endif()
# explicitly set hardening flags
@@ -111,12 +110,12 @@ set(CMD_MAKE make)
include(ExternalProject)
# LuaJIT
include(luajit)
# libs
include(falcosecurity-libs)
# LuaJit provided by libs
include(luajit)
# jq
include(jq)
@@ -144,9 +143,6 @@ if(NOT MINIMAL_BUILD)
# libcurl
include(curl)
# civetweb
include(civetweb)
endif()
# Lpeg
@@ -161,6 +157,24 @@ include(lyaml)
# One TBB
include(tbb)
if(NOT MINIMAL_BUILD)
# civetweb
set(CIVETWEB_SRC "${PROJECT_BINARY_DIR}/civetweb-prefix/src/civetweb/")
set(CIVETWEB_LIB "${CIVETWEB_SRC}/install/lib/libcivetweb.a")
set(CIVETWEB_INCLUDE_DIR "${CIVETWEB_SRC}/install/include")
message(STATUS "Using bundled civetweb in '${CIVETWEB_SRC}'")
ExternalProject_Add(
civetweb
URL "https://github.com/civetweb/civetweb/archive/v1.11.tar.gz"
URL_HASH "SHA256=de7d5e7a2d9551d325898c71e41d437d5f7b51e754b242af897f7be96e713a42"
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E make_directory ${CIVETWEB_SRC}/install/lib
COMMAND ${CMAKE_COMMAND} -E make_directory ${CIVETWEB_SRC}/install/include
BUILD_IN_SOURCE 1
BUILD_COMMAND ${CMD_MAKE} COPT="-DNO_FILES" WITH_CPP=1
BUILD_BYPRODUCTS ${CIVETWEB_LIB}
INSTALL_COMMAND ${CMD_MAKE} COPT="-DNO_FILES" install-lib install-headers PREFIX=${CIVETWEB_SRC}/install "WITH_CPP=1")
endif()
#string-view-lite
include(DownloadStringViewLite)
@@ -198,7 +212,6 @@ include(static-analysis)
# Shared build variables
set(FALCO_SINSP_LIBRARY sinsp)
set(FALCO_SHARE_DIR share/falco)
set(FALCO_PLUGINS_DIR ${FALCO_SHARE_DIR}/plugins)
set(FALCO_ABSOLUTE_SHARE_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}")
set(FALCO_BIN_DIR bin)
@@ -207,9 +220,5 @@ add_subdirectory(userspace/engine)
add_subdirectory(userspace/falco)
add_subdirectory(tests)
if(NOT MUSL_OPTIMIZED_BUILD)
include(plugins)
endif()
# Packages configuration
include(CPackConfig)

2
OWNERS
View File

@@ -4,7 +4,6 @@ approvers:
- leodido
- mstemm
- leogr
- jasondellaluce
reviewers:
- fntlnz
- kaizhe
@@ -13,4 +12,3 @@ reviewers:
- mfdii
- mstemm
- leogr
- jasondellaluce

View File

@@ -4,9 +4,7 @@ Our release process is mostly automated, but we still need some manual steps to
Changes and new features are grouped in [milestones](https://github.com/falcosecurity/falco/milestones), the milestone with the next version represents what is going to be released.
Falco releases are due to happen 3 times per year. Our current schedule sees a new release by the end of January, May, and September each year. Hotfix releases can happen whenever it's needed.
Moreover, we need to assign owners for each release (usually we pair a new person with an experienced one). Assignees and the due date are proposed during the [weekly community call](https://github.com/falcosecurity/community).
A release happens every two months ([as per community discussion](https://github.com/falcosecurity/community/blob/master/meeting-notes/2020-09-30.md#agenda)), and we need to assign owners for each (usually we pair a new person with an experienced one). Assignees and the due date are proposed during the [weekly community call](https://github.com/falcosecurity/community). Note that hotfix releases can happen as soon as it is needed.
Finally, on the proposed due date the assignees for the upcoming release proceed with the processes described below.

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

@@ -14,8 +14,8 @@ include(ExternalProject)
set(FAKEIT_INCLUDE ${CMAKE_BINARY_DIR}/fakeit-prefix/include)
set(FAKEIT_EXTERNAL_URL URL https://github.com/eranpeer/fakeit/archive/2.0.9.tar.gz URL_HASH
SHA256=dc4ee7b17a84c959019b92c20fce6dc9426e9e170b6edf84db6cb2e188520cd7)
set(FAKEIT_EXTERNAL_URL URL https://github.com/eranpeer/fakeit/archive/2.0.5.tar.gz URL_HASH
SHA256=298539c773baca6ecbc28914306bba19d1008e098f8adc3ad3bb00e993ecdf15)
ExternalProject_Add(
fakeit-external

View File

@@ -21,7 +21,7 @@ if(NOT FALCO_VERSION)
git_get_exact_tag(FALCO_TAG)
if(NOT FALCO_TAG)
# Obtain the closest tag
git_describe(FALCO_VERSION "--always" "--tags" "--abbrev=7")
git_describe(FALCO_VERSION "--always" "--tags")
# Fallback version
if(FALCO_VERSION MATCHES "NOTFOUND$")
set(FALCO_VERSION "0.0.0")
@@ -31,33 +31,29 @@ if(NOT FALCO_VERSION)
else()
# A tag has been found: use it as the Falco version
set(FALCO_VERSION "${FALCO_TAG}")
# Remove the starting "v" in case there is one
string(REGEX REPLACE "^v(.*)" "\\1" FALCO_VERSION "${FALCO_TAG}")
endif()
# TODO(leodido) > ensure Falco version is semver before extracting parts Populate partial version variables
string(REGEX MATCH "^(0|[1-9][0-9]*)" FALCO_VERSION_MAJOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\..*" "\\2" FALCO_VERSION_MINOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*).*" "\\3" FALCO_VERSION_PATCH
"${FALCO_VERSION}")
string(
REGEX
REPLACE
"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*).*"
"\\5"
FALCO_VERSION_PRERELEASE
"${FALCO_VERSION}")
if(FALCO_VERSION_PRERELEASE STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_PRERELEASE "")
endif()
if(NOT FALCO_VERSION_BUILD)
string(REGEX REPLACE ".*\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)" "\\1" FALCO_VERSION_BUILD "${FALCO_VERSION}")
endif()
if(FALCO_VERSION_BUILD STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_BUILD "")
endif()
endif()
# Remove the starting "v" in case there is one
string(REGEX REPLACE "^v(.*)" "\\1" FALCO_VERSION "${FALCO_VERSION}")
# TODO(leodido) > ensure Falco version is semver before extracting parts Populate partial version variables
string(REGEX MATCH "^(0|[1-9][0-9]*)" FALCO_VERSION_MAJOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\..*" "\\2" FALCO_VERSION_MINOR "${FALCO_VERSION}")
string(REGEX REPLACE "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*).*" "\\3" FALCO_VERSION_PATCH
"${FALCO_VERSION}")
string(
REGEX
REPLACE
"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*).*"
"\\5"
FALCO_VERSION_PRERELEASE
"${FALCO_VERSION}")
if(FALCO_VERSION_PRERELEASE STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_PRERELEASE "")
endif()
if(NOT FALCO_VERSION_BUILD)
string(REGEX REPLACE ".*\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)" "\\1" FALCO_VERSION_BUILD "${FALCO_VERSION}")
endif()
if(FALCO_VERSION_BUILD STREQUAL "${FALCO_VERSION}")
set(FALCO_VERSION_BUILD "")
endif()
message(STATUS "Falco version: ${FALCO_VERSION}")

View File

@@ -1,52 +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.
#
set(CIVETWEB_SRC "${PROJECT_BINARY_DIR}/civetweb-prefix/src/civetweb/")
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)
ExternalProject_Add(
civetweb
DEPENDS openssl
URL "https://github.com/civetweb/civetweb/archive/v1.15.tar.gz"
URL_HASH "SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9"
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off
-DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=off
-DCIVETWEB_SERVE_NO_FILES=on
-DCMAKE_INSTALL_PREFIX=${CIVETWEB_SRC}/install
-DOPENSSL_ROOT_DIR:PATH=${OPENSSL_INSTALL_DIR}
-DOPENSSL_USE_STATIC_LIBS:BOOL=TRUE
BUILD_BYPRODUCTS ${CIVETWEB_LIB} ${CIVETWEB_CPP_LIB})
else()
ExternalProject_Add(
civetweb
URL "https://github.com/civetweb/civetweb/archive/v1.15.tar.gz"
URL_HASH "SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9"
INSTALL_DIR ${CIVETWEB_SRC}/install
CMAKE_ARGS
-DBUILD_TESTING=off
-DCIVETWEB_BUILD_TESTING=off
-DCIVETWEB_ENABLE_CXX=on
-DCIVETWEB_ENABLE_SERVER_EXECUTABLE=off
-DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=off
-DCIVETWEB_SERVE_NO_FILES=on
-DCMAKE_INSTALL_PREFIX=${CIVETWEB_SRC}/install
BUILD_BYPRODUCTS ${CIVETWEB_LIB} ${CIVETWEB_CPP_LIB})
endif()

View File

@@ -24,4 +24,5 @@ ExternalProject_Add(
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
TEST_COMMAND ""
PATCH_COMMAND patch -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch/libscap.patch && patch -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch/luajit.patch)

View File

@@ -0,0 +1,49 @@
diff --git a/userspace/libscap/scap.c b/userspace/libscap/scap.c
index 6f51588e..5f9ea84e 100644
--- a/userspace/libscap/scap.c
+++ b/userspace/libscap/scap.c
@@ -55,7 +55,7 @@ limitations under the License.
//#define NDEBUG
#include <assert.h>
-static const char *SYSDIG_BPF_PROBE_ENV = "SYSDIG_BPF_PROBE";
+static const char *SYSDIG_BPF_PROBE_ENV = "FALCO_BPF_PROBE";
//
// Probe version string size
@@ -114,7 +114,7 @@ scap_t* scap_open_udig_int(char *error, int32_t *rc,
static uint32_t get_max_consumers()
{
uint32_t max;
- FILE *pfile = fopen("/sys/module/" PROBE_DEVICE_NAME "_probe/parameters/max_consumers", "r");
+ FILE *pfile = fopen("/sys/module/" PROBE_DEVICE_NAME "/parameters/max_consumers", "r");
if(pfile != NULL)
{
int w = fscanf(pfile, "%"PRIu32, &max);
@@ -186,7 +186,7 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
return NULL;
}
- snprintf(buf, sizeof(buf), "%s/.sysdig/%s-bpf.o", home, PROBE_NAME);
+ snprintf(buf, sizeof(buf), "%s/.falco/%s-bpf.o", home, PROBE_NAME);
bpf_probe = buf;
}
}
@@ -344,7 +344,7 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
else if(errno == EBUSY)
{
uint32_t curr_max_consumers = get_max_consumers();
- snprintf(error, SCAP_LASTERR_SIZE, "Too many sysdig instances attached to device %s. Current value for /sys/module/" PROBE_DEVICE_NAME "_probe/parameters/max_consumers is '%"PRIu32"'.", filename, curr_max_consumers);
+ snprintf(error, SCAP_LASTERR_SIZE, "Too many Falco instances attached to device %s. Current value for /sys/module/" PROBE_DEVICE_NAME "/parameters/max_consumers is '%"PRIu32"'.", filename, curr_max_consumers);
}
else
{
@@ -2175,7 +2175,7 @@ int32_t scap_disable_dynamic_snaplen(scap_t* handle)
const char* scap_get_host_root()
{
- char* p = getenv("SYSDIG_HOST_ROOT");
+ char* p = getenv("HOST_ROOT");
static char env_str[SCAP_MAX_PATH_SIZE + 1];
static bool inited = false;
if (! inited) {

View File

@@ -0,0 +1,57 @@
diff --git a/userspace/chisel/chisel.cpp b/userspace/chisel/chisel.cpp
index 0a6e3cf8..0c2e255a 100644
--- a/userspace/chisel/chisel.cpp
+++ b/userspace/chisel/chisel.cpp
@@ -98,7 +98,7 @@ void lua_stackdump(lua_State *L)
// Lua callbacks
///////////////////////////////////////////////////////////////////////////////
#ifdef HAS_LUA_CHISELS
-const static struct luaL_reg ll_sysdig [] =
+const static struct luaL_Reg ll_sysdig [] =
{
{"set_filter", &lua_cbacks::set_global_filter},
{"set_snaplen", &lua_cbacks::set_snaplen},
@@ -134,7 +134,7 @@ const static struct luaL_reg ll_sysdig [] =
{NULL,NULL}
};
-const static struct luaL_reg ll_chisel [] =
+const static struct luaL_Reg ll_chisel [] =
{
{"request_field", &lua_cbacks::request_field},
{"set_filter", &lua_cbacks::set_filter},
@@ -146,7 +146,7 @@ const static struct luaL_reg ll_chisel [] =
{NULL,NULL}
};
-const static struct luaL_reg ll_evt [] =
+const static struct luaL_Reg ll_evt [] =
{
{"field", &lua_cbacks::field},
{"get_num", &lua_cbacks::get_num},
diff --git a/userspace/chisel/lua_parser.cpp b/userspace/chisel/lua_parser.cpp
index 0e26617d..78810d96 100644
--- a/userspace/chisel/lua_parser.cpp
+++ b/userspace/chisel/lua_parser.cpp
@@ -32,7 +32,7 @@ extern "C" {
#include "lauxlib.h"
}
-const static struct luaL_reg ll_filter [] =
+const static struct luaL_Reg ll_filter [] =
{
{"rel_expr", &lua_parser_cbacks::rel_expr},
{"bool_op", &lua_parser_cbacks::bool_op},
diff --git a/userspace/chisel/lua_parser_api.cpp b/userspace/chisel/lua_parser_api.cpp
index c89e9126..c3d8008a 100644
--- a/userspace/chisel/lua_parser_api.cpp
+++ b/userspace/chisel/lua_parser_api.cpp
@@ -266,7 +266,7 @@ int lua_parser_cbacks::rel_expr(lua_State *ls)
string err = "Got non-table as in-expression operand\n";
throw sinsp_exception("parser API error");
}
- int n = luaL_getn(ls, 4); /* get size of table */
+ int n = lua_objlen (ls, 4); /* get size of table */
for (i=1; i<=n; i++)
{
lua_rawgeti(ls, 4, i);

View File

@@ -16,31 +16,26 @@ set(FALCOSECURITY_LIBS_CMAKE_WORKING_DIR "${CMAKE_BINARY_DIR}/falcosecurity-libs
file(MAKE_DIRECTORY ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR})
if(FALCOSECURITY_LIBS_SOURCE_DIR)
set(FALCOSECURITY_LIBS_VERSION "local")
message(STATUS "Using local falcosecurity/libs in '${FALCOSECURITY_LIBS_SOURCE_DIR}'")
else()
# The falcosecurity/libs git reference (branch name, commit hash, or tag) To update falcosecurity/libs version for the next release, change the
# 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 "319368f1ad778691164d33d59945e00c5752cd27")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=2cf44f06a282e8cee7aa1f775a08ea94c06e275faaf0636b21eb06af28cf4b3f")
endif()
# cd /path/to/build && cmake /path/to/source
execute_process(COMMAND "${CMAKE_COMMAND}" -DFALCOSECURITY_LIBS_VERSION=${FALCOSECURITY_LIBS_VERSION} -DFALCOSECURITY_LIBS_CHECKSUM=${FALCOSECURITY_LIBS_CHECKSUM}
${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR})
# todo(leodido, fntlnz) > use the following one when CMake version will be >= 3.13
# execute_process(COMMAND "${CMAKE_COMMAND}" -B ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR} WORKING_DIRECTORY
# "${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR}")
execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}")
set(FALCOSECURITY_LIBS_SOURCE_DIR "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}/falcosecurity-libs-prefix/src/falcosecurity-libs")
# The falcosecurity/libs git reference (branch name, commit hash, or tag) To update falcosecurity/libs version for the next release, change the
# 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 "new/plugin-system-api-additions")
set(FALCOSECURITY_LIBS_CHECKSUM "SHA256=ba0ea2e22121b8543cb1ebe616090097c4dc3f093db8f0bb5cf2ce5a7e0425a0")
endif()
# cd /path/to/build && cmake /path/to/source
execute_process(COMMAND "${CMAKE_COMMAND}" -DFALCOSECURITY_LIBS_VERSION=${FALCOSECURITY_LIBS_VERSION} -DFALCOSECURITY_LIBS_CHECKSUM=${FALCOSECURITY_LIBS_CHECKSUM}
${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR})
# todo(leodido, fntlnz) > use the following one when CMake version will be >= 3.13
# execute_process(COMMAND "${CMAKE_COMMAND}" -B ${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR} WORKING_DIRECTORY
# "${FALCOSECURITY_LIBS_CMAKE_SOURCE_DIR}")
execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}")
set(FALCOSECURITY_LIBS_SOURCE_DIR "${FALCOSECURITY_LIBS_CMAKE_WORKING_DIR}/falcosecurity-libs-prefix/src/falcosecurity-libs")
add_definitions(-D_GNU_SOURCE)
add_definitions(-DHAS_CAPTURE)
if(MUSL_OPTIMIZED_BUILD)
@@ -48,10 +43,6 @@ if(MUSL_OPTIMIZED_BUILD)
endif()
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")
if(NOT LIBSCAP_DIR)
set(LIBSCAP_DIR "${FALCOSECURITY_LIBS_SOURCE_DIR}")
@@ -69,19 +60,8 @@ 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")
include(CheckSymbolExists)
check_symbol_exists(strlcpy "string.h" HAVE_STRLCPY)
if(HAVE_STRLCPY)
message(STATUS "Existing strlcpy found, will *not* use local definition by setting -DHAVE_STRLCPY.")
add_definitions(-DHAVE_STRLCPY)
else()
message(STATUS "No strlcpy found, will use local definition")
endif()
include(libscap)
include(libsinsp)

View File

@@ -0,0 +1,30 @@
#
# 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.
#
if(NOT LUAJIT_INCLUDE)
set(LUAJIT_SRC "${PROJECT_BINARY_DIR}/luajit-prefix/src/luajit/src")
message(STATUS "Using bundled LuaJIT in '${LUAJIT_SRC}'")
set(LUAJIT_INCLUDE "${LUAJIT_SRC}")
set(LUAJIT_LIB "${LUAJIT_SRC}/libluajit.a")
externalproject_add(
luajit
GIT_REPOSITORY "https://github.com/LuaJIT/LuaJIT"
GIT_TAG "1d8b747c161db457e032a023ebbff511f5de5ec2"
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
BUILD_BYPRODUCTS ${LUAJIT_LIB}
INSTALL_COMMAND ""
)
endif()
include_directories("${LUAJIT_INCLUDE}")

View File

@@ -11,10 +11,9 @@
# 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}'")
set(LYAML_SRC "${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/ext/yaml")
set(LYAML_LIB "${LYAML_SRC}/.libs/yaml.a")
message(STATUS "Using bundled lyaml in '${LYAML_SRC}'")
externalproject_add(
lyaml
DEPENDS luajit libyaml
@@ -23,6 +22,7 @@ externalproject_add(
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}
INSTALL_COMMAND sh -c
"cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/engine/lua"
)

View File

@@ -1,36 +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(ExternalProject)
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} PLUGINS_SYSTEM_NAME)
ExternalProject_Add(
cloudtrail-plugin
URL "https://download.falco.org/plugins/stable/cloudtrail-0.2.2-${PLUGINS_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=1628717e48b2ba1b9c78c9081e2ec23e4d88bb1a7b68b12cf8dff7f247b5b9b1"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
install(FILES "${PROJECT_BINARY_DIR}/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so" DESTINATION "${FALCO_PLUGINS_DIR}")
ExternalProject_Add(
json-plugin
URL "https://download.falco.org/plugins/stable/json-0.2.1-${PLUGINS_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}.tar.gz"
URL_HASH "SHA256=14d1cf4c3c651af0daec7a45162ef91172d6f0baba787f0eff0227b3cf2ca39c"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
install(FILES "${PROJECT_BINARY_DIR}/json-plugin-prefix/src/json-plugin/libjson.so" DESTINATION "${FALCO_PLUGINS_DIR}")

View File

@@ -18,7 +18,7 @@ How to use.
* docker run -ti falcosecurity/falco-builder bash
To build Falco it needs:
- a bind-mount on the source directory (ie., the directory containing the Falco source as sibling)
- a bind-mount on the source directory (ie., the directory containing Falco and sysdig source as siblings)
Optionally, you can also bind-mount the build directory.
So, you can execute it from the Falco root directory as follows.

View File

@@ -1,4 +1,4 @@
FROM debian:buster
FROM debian:stable
LABEL maintainer="cncf-falco-dev@lists.cncf.io"
@@ -30,7 +30,6 @@ RUN apt-get update \
libc6-dev \
libelf-dev \
libmpx2 \
libssl-dev \
llvm-7 \
netcat \
xz-utils \

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

@@ -33,32 +33,6 @@ rules_file:
- /etc/falco/k8s_audit_rules.yaml
- /etc/falco/rules.d
#
# Plugins that are available for use. These plugins are not loaded by
# default, as they require explicit configuration to point to
# cloudtrail log files.
#
# To learn more about the supported formats for
# init_config/open_params for the cloudtrail plugin, see the README at
# https://github.com/falcosecurity/plugins/blob/master/plugins/cloudtrail/README.md.
plugins:
- name: cloudtrail
library_path: libcloudtrail.so
init_config: ""
open_params: ""
- name: json
library_path: libjson.so
init_config: ""
# Setting this list to empty ensures that the above plugins are *not*
# loaded and enabled by default. If you want to use the above plugins,
# set a meaningful init_config/open_params for the cloudtrail plugin
# and then change this to:
# load_plugins: [cloudtrail, json]
load_plugins: []
# If true, the times displayed in log messages and output messages
# will be in ISO 8601. By default, times are displayed in the local
# time zone, as governed by /etc/localtime.
@@ -72,12 +46,6 @@ json_output: false
# (user=root ....") in the json output.
json_include_output_property: true
# When using json output, whether or not to include the "tags" property
# itself in the json output. If set to true, outputs caused by rules
# with no tags will have a "tags" field set to an empty array. If set to
# false, the "tags" field will not be included in the json output at all.
json_include_tags_property: true
# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: true
@@ -92,7 +60,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 +214,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)
@@ -282,9 +249,3 @@ grpc:
# Make sure to have a consumer for them or leave this disabled.
grpc_output:
enabled: false
# Container orchestrator metadata fetching params
metadata_download:
max_mb: 100
chunk_wait_us: 1000
watch_freq_sec: 1

View File

@@ -2,8 +2,6 @@
This document reflects when and how we clean up the Falco artifacts from their storage location.
**Superseeded by**: [drivers-storage-s3 proposal](https://github.com/falcosecurity/falco/blob/master/proposals/20201025-drivers-storage-s3.md).
## Motivation
The [bintray](https://bintray.com/falcosecurity) open-source plan offers 10GB free space for storing artifacts.
@@ -96,19 +94,9 @@ Since the process of building drivers is time and resource consuming, this docum
The candidate is an AWS S3 bucket responsible for holding the deleted driver version files.
#### Notice
The current mechanism the Falco community uses to store the Falco drivers is explained by the [drivers-storage-s3](https://github.com/falcosecurity/falco/blob/master/proposals/20201025-drivers-storage-s3.md) proposal.
### Implementation
The [test-infra](https://github.com/falcosecurity/test-infra) CI, specifically its part dedicated to run the **Drivers Build Grid** that runs every time it detects changes into the `driverkit` directory of the [test-infra](https://github.com/falcosecurity/test-infra) repository,
will have a new job - called `drivers/cleanup` - responsible for removing all the Falco driver versions except the last two.
This job will be triggered after the `drivers/publish` completed successfully on the master branch.
#### Notice
At the moment of writing (2021 09 28) the `drivers/cleanup` job is no more in place.
Pragmatically, this means that the older Falco drivers will remain available in their [S3 bucket](https://download.falco.org/?prefix=driver/).
This job will be triggered after the `drivers/publish` completed successfully on the master branch.

View File

@@ -1,613 +0,0 @@
# Plugin System
## Summary
This is a proposal to create an infrastructure to extend the functionality of the Falco libraries via plugins.
Plugins will allow users to easily extend the functionality of the libraries and, as a consequence, of Falco and any other tool based on the libraries.
This proposal, in particular, focuses on two types of plugins: source plugins and extractor plugins.
## Motivation
[libscap](https://github.com/falcosecurity/libs/tree/master/userspace/libscap) and [libsinsp](https://github.com/falcosecurity/libs/tree/master/userspace/libsinsp) provide a powerful data capture framework, with a rich set of features that includes:
- data capture
- trace files management
- enrichment
- filtering
- formatting and screen rendering
- Lua scripting (chisels)
These features have been designed with one specific input in mind: system calls. However, they are generically adaptable to a broad set of inputs, such as cloud logs.
With this proposal, we want to dramatically extend the scope of what the libraries, Falco and other tools can be applied to. We want to do it in a way that is easy, efficient and empowers anyone in the community to write a plugin.
## Goals
- To design and implement a plugin framework that makes the libraries more modular and extensible
- To have a framework that is easy to use
- To support dynamic loading of plugins, so that the libraries can be extended without having to be recompiled and relinked
- To enable users to write plugins in any language, with a particular focus on Go, C and C++
- To have an efficient plugin framework so that, performance-wise, writing a plugin is as close as possible as extending the libraries internal source code
- To make it possible to write plugins for Linux, MacOS and Windows
## Non-Goals
- To implement plugins other than source and extractor: to be approached as separate task
- To document the plugin framework and interface: to be approached as separate task
## Proposal
### Plugin Common Information
Both source and extractor plugins have the following:
- A required api version, to ensure compatibility with the plugin framework.
- A name
- A description
- A version
- A contact field for the plugin authors (website, github repo, twitter, etc).
- Functions to initialize and destroy the plugin internal state.
### Plugin types
Initially, we will implement support for two types of plugins: source plugins and extractor plugins.
#### Source Plugin
A source plugin implements a new sinsp/scap event source. It has the ability to "open" and "close" a session that provides events. It also has the ability to return an event to the plugin framework via a next() method. Events returned by source plugins have an "event source", which describes the information in the event. This is distinct from the plugin name to allow for multiple kinds of plugins to generate the same kind of events. For example, there might be plugins gke-audit-bridge, eks-audit-bridge, ibmcloud-audit-bridge, etc. that all fetch [K8s Audit](https://kubernetes.io/docs/tasks/debug-application-cluster/audit/) information. The plugins would have different names but would have the same event source "k8s_audit".
Source plugins also have the ability to extract information from events based on fields. For example, a field proc.name extracts a process name from a syscall event. The plugin returns a set of supported fields, and there are functions to extract a value given an event and field. The plugin framework can then build filtering expressions/Falco rule conditions based on these fields combined with relational and/or logical operators. For example, given an expression "ct.name=root and ct.region=us-east-1", the plugin framework handles parsing the expression, calling the plugin to extract values for a given event, and determining the result of the expression. In a Falco output string like "An EC2 Node was created (name=%ec2.name region=%ct.region)", the plugin framework handles parsing the output string, calling the plugin to extract values for a given event, and building the resolved string.
Source plugins also provide an "id", which is globally unique and is used in capture files (see below).
#### Extractor Plugin
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 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.
libscap will define a new event type called "pluginevent" that contains two fields:
* "plugin ID": This uniquely identifies the plugin that generated this event.
* "event_data": This is a variable-length data buffer containing the event data, as returned by the plugin.
Defining an event for plugins allows creating capture files from plugins. These capture files can be saved, read, filtered, etc, like any other capture file, allowing for later analysis/display/etc.
### Plugins format
Plugins are dynamic libraries (.so files in Unix, .dll files in windows) that export a minimum set of functions that the libraries will recognize.
Plugins are versioned using semantic versioning to minimize regressions and compatibility issues.
Plugins can be written in any language, as long as they export the required functions. Go, however, is the preferred language to write plugins, followed by C/C++.
### Protecting from plugin issues
The libraries will do everything possible to validate the data coming from the plugins and protect Falco and the other consumers from corrupted data. However, for performance reasons, plugins will be "trusted": they will run in the same thread and address space as Falco and they could crash the program. We assume that the user will be in control of plugin loading and will make sure only trusted plugins are loaded/packaged with Falco.
### 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 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.
Similarly, plugin developers must register event sources with the Falco organization. This allows coordination between plugins that wish to provide compatible payloads, and to allow extractor plugins to know what data format is associated with a given event source.
### golang plugin SDK
To facilitate the development of plugins written in go, an SDK has been developed. We intend this SDK (and future SDKs for other languages) to be part of the Falco organization. For this reason, we submitted the following incubation request: https://github.com/falcosecurity/evolution/issues/62
### Proposed API (subject to change)
```c
// This struct represents an event returned by the plugin, and is used
// below in next()/next_batch().
// - data: pointer to a memory buffer pointer. The plugin will set it
// to point to the memory containing the next event. Once returned,
// the memory is owned by the plugin framework and will be freed via
// a call to free().
// - datalen: pointer to a 32bit integer. The plugin will set it the size of the
// buffer pointed by data.
// - ts: the event timestamp. Can be (uint64_t)-1, in which case the engine will
// automatically fill the event time with the current time.
typedef struct ss_plugin_event
{
uint8_t *data;
uint32_t datalen;
uint64_t ts;
} ss_plugin_event;
//
// This is the opaque pointer to the state of a plugin.
// It points to any data that might be needed plugin-wise. It is
// allocated by init() and must be destroyed by destroy().
// It is defined as void because the engine doesn't care what it is
// and it treats is as opaque.
//
typedef void ss_plugin_t;
//
// This is the opaque pointer to the state of an open instance of the source
// plugin.
// It points to any data that is needed while a capture is running. It is
// allocated by open() and must be destroyed by close().
// It is defined as void because the engine doesn't care what it is
// and it treats is as opaque.
//
typedef void ss_instance_t;
//
// Interface for a sinsp/scap source plugin
//
//
// NOTE: For all functions below that return a char *, the memory
// pointed to by the char * must be allocated by the plugin using
// malloc() and should be freed by the caller using free().
//
// For each function below, the exported symbol from the dynamic
// library should have a prefix of "plugin_"
// (e.g. plugin_get_required_api_version, plugin_init, etc.)
//
typedef struct
{
//
// Return the version of the plugin API used by this plugin.
// Required: yes
// Return value: the API version string, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// NOTE: to ensure correct interoperability between the engine and the plugins,
// we use a semver approach. Plugins are required to specify the version
// of the API they run against, and the engine will take care of checking
// and enforcing compatibility.
//
char* (*get_required_api_version)();
//
// Return the plugin type.
// Required: yes
// Should return TYPE_SOURCE_PLUGIN. It still makes sense to
// have a function get_type() as the plugin interface will
// often dlsym() functions from shared libraries, and can't
// inspect any C struct type.
//
uint32_t (*get_type)();
//
// Initialize the plugin and, if needed, allocate its state.
// Required: yes
// Arguments:
// - config: a string with the plugin configuration. The format of the
// string is chosen by the plugin itself.
// - rc: pointer to an integer that will contain the initialization result,
// as a SCAP_* value (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1)
// Return value: pointer to the plugin state that will be treated as opaque
// by the engine and passed to the other plugin functions.
// If rc is SCAP_FAILURE, this function should return NULL.
//
ss_plugin_t* (*init)(char* config, int32_t* rc);
//
// Destroy the plugin and, if plugin state was allocated, free it.
// Required: yes
//
void (*destroy)(ss_plugin_t* s);
//
// Return a string with the error that was last generated by
// the plugin.
// Required: yes
//
// In cases where any other api function returns an error, the
// plugin should be prepared to return a human-readable error
// string with more context for the error. The plugin manager
// calls get_last_error() to access that string.
//
char* (*get_last_error)(ss_plugin_t* s);
//
// Return the unique ID of the plugin.
// Required: yes
// EVERY SOURCE PLUGIN (see get_type()) MUST OBTAIN AN OFFICIAL ID FROM THE
// FALCOSECURITY ORGANIZATION, OTHERWISE IT WON'T PROPERLY COEXIST WITH OTHER PLUGINS.
//
uint32_t (*get_id)();
//
// Return the name of the plugin, which will be printed when displaying
// information about the plugin.
// Required: yes
//
char* (*get_name)();
//
// Return the descriptions of the plugin, which will be printed when displaying
// information about the plugin or its events.
// Required: yes
//
char* (*get_description)();
//
// Return a string containing contact info (url, email, twitter, etc) for
// the plugin authors.
// Required: yes
//
char* (*get_contact)();
//
// Return the version of this plugin itself
// Required: yes
// Return value: a string with a version identifier, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// This differs from the api version in that this versions the
// plugin itself, as compared to the plugin interface. When
// reading capture files, the major version of the plugin that
// generated events must match the major version of the plugin
// used to read events.
//
char* (*get_version)();
//
// Return a string describing the events generated by this source plugin.
// Required: yes
// Example event sources would be strings like "syscall",
// "k8s_audit", etc. The source can be used by extractor
// plugins to filter the events they receive.
//
char* (*get_event_source)();
//
// Return the list of extractor fields exported by this plugin. Extractor
// fields can be used in Falco rule conditions and sysdig filters.
// Required: no
// Return value: a string with the list of fields encoded as a json
// array.
// Each field entry is a json object with the following properties:
// "type": one of "string", "uint64"
// "name": a string with a name for the field
// "desc": a string with a description of the field
// Example return value:
// [
// {"type": "string", "name": "field1", "desc": "Describing field 1"},
// {"type": "uint64", "name": "field2", "desc": "Describing field 2"}
// ]
char* (*get_fields)();
//
// Open the source and start a capture.
// Required: yes
// Arguments:
// - s: the plugin state returned by init()
// - params: the open parameters, as a string. The format is defined by the plugin
// 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(),
// close(), event_to_string() and extract_*.
//
ss_instance_t* (*open)(ss_plugin_t* s, char* params, int32_t* rc);
//
// Close a capture.
// Required: yes
// Arguments:
// - s: the plugin context, returned by init(). Can be NULL.
// - h: the capture context, returned by open(). Can be NULL.
//
void (*close)(ss_plugin_t* s, ss_instance_t* h);
//
// Return the next event.
// Required: yes
// Arguments:
// - s: the plugin context, returned by init(). Can be NULL.
// - h: the capture context, returned by open(). Can be NULL.
//
// - evt: pointer to a ss_plugin_event pointer. The plugin should
// allocate a ss_plugin_event struct using malloc(), as well as
// allocate the data buffer within the ss_plugin_event struct.
// Both the struct and data buffer are owned by the plugin framework
// and will free them using free().
//
// Return value: the status of the operation (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1,
// SCAP_TIMEOUT=-1)
//
int32_t (*next)(ss_plugin_t* s, ss_instance_t* h, ss_plugin_event **evt);
//
// Return the read progress.
// Required: no
// Arguments:
// - progress_pct: the read progress, as a number between 0 (no data has been read)
// and 10000 (100% of the data has been read). This encoding allows the engine to
// print progress decimals without requiring to deal with floating point numbers
// (which could cause incompatibility problems with some languages).
// Return value: a string representation of the read
// progress. This might include the progress percentage
// combined with additional context added by the plugin. If
// NULL, progress_pct should be used.
// NOTE: reporting progress is optional and in some case could be impossible. However,
// when possible, it's recommended as it provides valuable information to the
// user.
//
char* (*get_progress)(ss_plugin_t* s, ss_instance_t* h, uint32_t* progress_pct);
//
// Return a text representation of an event generated by this source plugin.
// Required: yes
// Arguments:
// - data: the buffer from an event produced by next().
// - datalen: the length of the buffer from an event produced by next().
// Return value: the text representation of the event. This is used, for example,
// by sysdig to print a line for the given event.
//
char *(*event_to_string)(ss_plugin_t *s, const uint8_t *data, uint32_t datalen);
//
// Extract a filter field value from an event.
// We offer multiple versions of extract(), differing from each other only in
// the type of the value they return (string, integer...).
// Required: no
// Arguments:
// - evtnum: the number of the event that is bein processed
// - id: the numeric identifier of the field to extract. It corresponds to the
// position of the field in the array returned by get_fields().
// - arg: the field argument, if an argument has been specified for the field,
// otherwise it's NULL. For example:
// * if the field specified by the user is foo.bar[pippo], arg will be the
// string "pippo"
// * if the field specified by the user is foo.bar, arg will be NULL
// - data: the buffer produced by next().
// - datalen: the length of the buffer produced by next().
// - field_present: nonzero if the field is present for the given event.
// Return value: the produced value of the filter field. For extract_str(), a
// NULL return value means that the field is missing for the given event.
//
char *(*extract_str)(ss_plugin_t *s, uint64_t evtnum, const char * field, const char *arg, uint8_t *data, uint32_t datalen);
uint64_t (*extract_u64)(ss_plugin_t *s, uint64_t evtnum, const char *field, const char *arg, uint8_t *data, uint32_t datalen, uint32_t *field_present);
//
// This is an optional, internal, function used to speed up event capture by
// batching the calls to next().
// On success:
// - nevts will be filled in with the number of events.
// - evts: pointer to an ss_plugin_event pointer. The plugin should
// allocate an array of contiguous ss_plugin_event structs using malloc(),
// as well as allocate each data buffer within each ss_plugin_event
// struct using malloc(). Both the array of structs and each data buffer are
// owned by the plugin framework and will free them using free().
// Required: no
//
int32_t (*next_batch)(ss_plugin_t* s, ss_instance_t* h, uint32_t *nevts, ss_plugin_event **evts);
//
// This is an optional, internal, function used to speed up value extraction
// Required: no
//
int32_t (*register_async_extractor)(ss_plugin_t *s, async_extractor_info *info);
//
// The following members are PRIVATE for the engine and should not be touched.
//
ss_plugin_t* state;
ss_instance_t* handle;
uint32_t id;
char *name;
} source_plugin_info;
//
// Interface for a sinsp/scap extractor plugin
//
//
// NOTE: For all functions below that return a char *, the memory
// pointed to by the char * must be allocated by the plugin using
// malloc() and should be freed by the caller using free().
//
typedef struct
{
//
// Return the version of the plugin API used by this plugin.
// Required: yes
// Return value: the API version string, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// NOTE: to ensure correct interoperability between the engine and the plugins,
// we use a semver approach. Plugins are required to specify the version
// of the API they run against, and the engine will take care of checking
// and enforcing compatibility.
//
char* (*get_required_api_version)();
//
// Return the plugin type.
// Required: yes
// Should return TYPE_EXTRACTOR_PLUGIN. It still makes sense to
// have a function get_type() as the plugin interface will
// often dlsym() functions from shared libraries, and can't
// inspect any C struct type.
//
uint32_t (*get_type)();
//
// Initialize the plugin and, if needed, allocate its state.
// Required: yes
// Arguments:
// - config: a string with the plugin configuration. The format of the
// string is chosen by the plugin itself.
// - rc: pointer to an integer that will contain the initialization result,
// as a SCAP_* value (e.g. SCAP_SUCCESS=0, SCAP_FAILURE=1)
// Return value: pointer to the plugin state that will be treated as opaque
// by the engine and passed to the other plugin functions.
//
ss_plugin_t* (*init)(char* config, int32_t* rc);
//
// Destroy the plugin and, if plugin state was allocated, free it.
// Required: yes
//
void (*destroy)(ss_plugin_t* s);
//
// Return a string with the error that was last generated by
// the plugin.
// Required: yes
//
// In cases where any other api function returns an error, the
// plugin should be prepared to return a human-readable error
// string with more context for the error. The plugin manager
// calls get_last_error() to access that string.
//
char* (*get_last_error)(ss_plugin_t* s);
//
// Return the name of the plugin, which will be printed when displaying
// information about the plugin.
// Required: yes
//
char* (*get_name)();
//
// Return the descriptions of the plugin, which will be printed when displaying
// information about the plugin or its events.
// Required: yes
//
char* (*get_description)();
//
// Return a string containing contact info (url, email, twitter, etc) for
// the plugin author.
// Required: yes
//
char* (*get_contact)();
//
// Return the version of this plugin itself
// Required: yes
// Return value: a string with a version identifier, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// This differs from the api version in that this versions the
// plugin itself, as compared to the plugin interface. When
// reading capture files, the major version of the plugin that
// generated events must match the major version of the plugin
// used to read events.
//
char* (*get_version)();
//
// Return a string describing the event sources that this
// extractor plugin can consume.
// Required: no
// 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 exctractor
// plugin will receive every event.
//
char* (*get_extract_event_sources)();
//
// Return the list of extractor fields exported by this plugin. Extractor
// fields can be used in Falco rules and sysdig filters.
// Required: yes
// Return value: a string with the list of fields encoded as a json
// array.
//
char* (*get_fields)();
//
// Extract a filter field value from an event.
// We offer multiple versions of extract(), differing from each other only in
// the type of the value they return (string, integer...).
// Required: for plugins of type TYPE_EXTRACTOR_PLUGIN only
// Arguments:
// - evtnum: the number of the event that is being processed
// - id: the numeric identifier of the field to extract. It corresponds to the
// position of the field in the array returned by get_fields().
// - arg: the field argument, if an argument has been specified for the field,
// otherwise it's NULL. For example:
// * if the field specified by the user is foo.bar[pippo], arg will be the
// string "pippo"
// * if the field specified by the user is foo.bar, arg will be NULL
// - data: the buffer produced by next().
// - datalen: the length of the buffer produced by next().
// - field_present: nonzero if the field is present for the given event.
// Return value: the produced value of the filter field. For extract_str(), a
// NULL return value means that the field is missing for the given event.
//
char *(*extract_str)(ss_plugin_t *s, uint64_t evtnum, const char *field, const char *arg, uint8_t *data, uint32_t datalen);
uint64_t (*extract_u64)(ss_plugin_t *s, uint64_t evtnum, const char *field, const char *arg, uint8_t *data, uint32_t datalen, uint32_t *field_present);
} extractor_plugin_info;
```
### Event Sources and Falco Rules
Falco rules already have the notion of a "source", using the source property in rules objects, and there are currently two kinds of event sources: "syscall" and "k8s_audit". We will use the source property in Falco rules to map a given rule to the event source on which the rule runs.
For example, given a plugin with source "aws_cloudtrail", and a Falco rule with source "aws_cloudtrail", the rule will be evaluated for any events generated by the plugin.
Similarly, an extractor plugin that includes "aws_cloudtrail" in its set of event sources will have the opportunity to extract information from aws_cloudtrail events if a matching field is found in the rule's condition, exception, or output properties.
This, combined with the restrictions below, allows a set of loaded rules files to contain a mix of rules for plugins as well as "core" syscall/k8s_audit events.
We will also make a change to compile rules/macros/lists selectively based on the set of loaded plugins (specifically, their event sources), instead of unconditionally as Falco is started. This is especially important for macros, which do not contain a source property, but might contain fields that are only implemented by a given plugin.
### 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 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.
We only need to ensure the following:
* That only one plugin is loaded at a time that exports a given event source. For example, the libraries can load either a gke-audit-bridge plugin with event source k8s_audit, or eks-audit-bridge with event source k8s_audit, but not both.
* That for a mix of source and extractor plugins having the same event source, that the fields are distinct. For example, a source plugin with source k8s_audit can export ka.* fields, and an extractor plugin with event source k8s_audit can export a jevt.value[/...] field, and the appropriate plugin will be used to extract fields from k8s_audit events as fields are parsed from condition expressions/output format strings.
### Plugin Versions and Falco Rules
To allow rules files to document the plugin versions they are compatible with, we will add a new top-level field `required_plugin_versions` to the Falco rules file format. The field is optional, and if not provided no plugin compatibility checks will be performed. The syntax of `required_plugin_versions` will be the following:
```yaml
- required_plugin_versions:
- name: <plugin_name>
version: x.y.z
...
```
Below required_plugin_versions is a list of objects, where each object has `name` and `version` properties. If a plugin is loaded, and if an entry in `required_plugin_versions` has a matching name, then the loaded plugin version must be semver compatible with the version property.
Falco can load multiple rules files, and each file may contain its own `required_plugin_versions` property. In this case, name+version pairs across all files will be merged, and in the case of duplicate names all provided versions must be compatible.
### Loading the plugins
The mechanics of loading a plugin are implemented in the libraries and leverage the dynamic library functionality of the operating system (dlopen/dlsym in unix, LoadLibrary/GetProcAddress in Windows). The plugin loading code also ensures that:
- the plugin is valid, i.e. that it exports the set of expected symbols
- the plugin has an api version number that is compatible with the libraries instance
- that only one source plugin is loaded at a time for a given event source
- if a mix of source and extractor plugins are loaded for a given event source, that the exported fields have unique names that don't overlap across plugins
#### Loading plugins in falcosecurity/libs
At the libraries level, loading plugins is handled via the static method:
```c++
void sinsp_plugin::register_plugin(sinsp* inspector, string filepath, char* config, ...)
```
filepath points to a dynamic library containing code that exports plugin API functions. config contains arbitrary config content which is passed to init().
Note that the code using the libraries is responsible for determining the location of plugin libraries and their configuration.
#### Loading plugins in falcosecurity/falco
Falco will control/configure loading plugins via the new "plugins" property in falco.yaml. Here's an example:
```yaml
plugins:
- name: aws_cloudtrail
library_path: aws_cloudtrail/plugin.so
init_config: "..."
open_params: "..."
- name: http_json
library_path: http_json/plugin.so
init_config_file: http_json/config.txt
open_params_file: http_json/params.txt
# Optional
load_plugins: [aws_cloudtrail]
```
A new "plugins" property in falco.yaml will define the set of plugins that can be loaded by Falco. The property contains a list of objects, with the following properties:
* name: Only used for load_plugins, but by convention should be the same as the value returned by the name() api function.
* library_path: a path to the shared library. The path can be relative, in which case it is relative to Falco's "share" directory under a "plugins" subdirectory e.g. /usr/share/falco/plugins.
* init_config: If present, the exact configuration text that will be provided as an argument to the init() function.
* init_config_file: If present, the provided file will be read and the contents will be provided as an argument to the init() function.
* open_params: If present, the exact params text that will be provided as an argument to the open() function.
* open_params_file: If present, the provided file will be read and the contents will be provided as an argument to the open() function.
For a given yaml object in the plugins list, only one of init_config/init_config_file and one of open_params/open_params_file can be provided at a time.
A new "load_plugins" property in falco.yaml will allow for loading a subset of the plugins defined in plugins. If present, only the plugins with the provided names will be loaded.
### Examples
We have an initial version working, consisting of:
* A version of falcosecurity/libs that supports the [plugin framework](https://github.com/falcosecurity/libs/tree/new/plugin-system-api-additions)
* Support code and examples for [writing plugins in go](https://github.com/mstemm/libsinsp-plugin-sdk-go/tree/new/plugin-system-api-additions)
* A [cloudtrail](https://github.com/mstemm/plugins/tree/new/plugin-system-api-additions) plugin that can generate events from cloudtrail logs and extract fields from those events.
* A version of Falco that uses all of the above to [load and evaluate rules with plugins](https://github.com/leogr/falco/tree/new/plugin-system-api-additions)

View File

@@ -23,7 +23,6 @@ if(NOT DEFINED FALCO_RULES_DEST_FILENAME)
set(FALCO_LOCAL_RULES_DEST_FILENAME "falco_rules.local.yaml")
set(FALCO_APP_RULES_DEST_FILENAME "application_rules.yaml")
set(FALCO_K8S_AUDIT_RULES_DEST_FILENAME "k8s_audit_rules.yaml")
set(FALCO_AWS_CLOUDTRAIL_RULES_DEST_FILENAME "aws_cloudtrail_rules.yaml")
endif()
if(DEFINED FALCO_COMPONENT)
@@ -60,10 +59,5 @@ else()
DESTINATION "${FALCO_ETC_DIR}/rules.available"
RENAME "${FALCO_APP_RULES_DEST_FILENAME}")
install(
FILES aws_cloudtrail_rules.yaml
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_AWS_CLOUDTRAIL_RULES_DEST_FILENAME}")
install(DIRECTORY DESTINATION "${FALCO_ETC_DIR}/rules.d")
endif()

View File

@@ -1,442 +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.
#
# All rules files related to plugins should require engine version 10
- required_engine_version: 10
# These rules can be read by cloudtrail plugin version 0.1.0, or
# anything semver-compatible.
- required_plugin_versions:
- name: cloudtrail
version: 0.2.2
- name: json
version: 0.2.1
# Note that this rule is disabled by default. It's useful only to
# verify that the cloudtrail plugin is sending events properly. The
# very broad condition evt.num > 0 only works because the rule source
# is limited to aws_cloudtrail. This ensures that the only events that
# are matched against the rule are from the cloudtrail plugin (or
# a different plugin with the same source).
- rule: All Cloudtrail Events
desc: Match all cloudtrail events.
condition:
evt.num > 0
output: Some Cloudtrail Event (evtnum=%evt.num info=%evt.plugininfo ts=%evt.time.iso8601 id=%ct.id error=%ct.error)
priority: DEBUG
tags:
- cloud
- aws
source: aws_cloudtrail
enabled: false
- rule: Console Login Through Assume Role
desc: Detect a console login through Assume Role.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and ct.user.identitytype="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
output:
Detected a console login through Assume Role
(principal=%ct.user.principalid,
assumedRole=%ct.user.arn,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: WARNING
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail
- rule: Console Login Without MFA
desc: Detect a console login without MFA.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and ct.user.identitytype!="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
and json.value[/additionalEventData/MFAUsed]="No"
output:
Detected a console login without MFA
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: CRITICAL
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail
- rule: Console Root Login Without MFA
desc: Detect root console login without MFA.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and json.value[/additionalEventData/MFAUsed]="No"
and ct.user.identitytype!="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
and ct.user.identitytype="Root"
output:
Detected a root console login without MFA.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: CRITICAL
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail
- rule: Deactivate MFA for Root User
desc: Detect deactivating MFA configuration for root.
condition:
ct.name="DeactivateMFADevice" and not ct.error exists
and ct.user.identitytype="Root"
and ct.request.username="AWS ROOT USER"
output:
Multi Factor Authentication configuration has been disabled for root
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
MFA serial number=%ct.request.serialnumber)
priority: CRITICAL
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: Create AWS user
desc: Detect creation of a new AWS user.
condition:
ct.name="CreateUser" and not ct.error exists
output:
A new AWS user has been created
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
new user created=%ct.request.username)
priority: INFO
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: Create Group
desc: Detect creation of a new user group.
condition:
ct.name="CreateGroup" and not ct.error exists
output:
A new user group has been created.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
group name=%ct.request.groupname)
priority: WARNING
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: Delete Group
desc: Detect deletion of a user group.
condition:
ct.name="DeleteGroup" and not ct.error exists
output:
A user group has been deleted.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
group name=%ct.request.groupname)
priority: WARNING
tags:
- cloud
- aws
- aws_iam
source: aws_cloudtrail
- rule: ECS Service Created
desc: Detect a new service is created in ECS.
condition:
ct.src="ecs.amazonaws.com" and
ct.name="CreateService" and
not ct.error exists
output:
A new service has been created in ECS
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
cluster=%ct.request.cluster,
service name=%ct.request.servicename,
task definition=%ct.request.taskdefinition)
priority: WARNING
tags:
- cloud
- aws
- aws_ecs
- aws_fargate
source: aws_cloudtrail
- rule: ECS Task Run or Started
desc: Detect a new task is started in ECS.
condition:
ct.src="ecs.amazonaws.com" and
(ct.name="RunTask" or ct.name="StartTask") and
not ct.error exists
output:
A new task has been started in ECS
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
cluster=%ct.request.cluster,
task definition=%ct.request.taskdefinition)
priority: WARNING
tags:
- cloud
- aws
- aws_ecs
- aws_fargate
source: aws_cloudtrail
- rule: Create Lambda Function
desc: Detect creation of a Lambda function.
condition:
ct.name="CreateFunction20150331" and not ct.error exists
output:
Lambda function has been created.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
lambda function=%ct.request.functionname)
priority: WARNING
tags:
- cloud
- aws
- aws_lambda
source: aws_cloudtrail
- rule: Update Lambda Function Code
desc: Detect updates to a Lambda function code.
condition:
ct.name="UpdateFunctionCode20150331v2" and not ct.error exists
output:
The code of a Lambda function has been updated.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
lambda function=%ct.request.functionname)
priority: WARNING
tags:
- cloud
- aws
- aws_lambda
source: aws_cloudtrail
- rule: Update Lambda Function Configuration
desc: Detect updates to a Lambda function configuration.
condition:
ct.name="UpdateFunctionConfiguration20150331v2" and not ct.error exists
output:
The configuration of a Lambda function has been updated.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
lambda function=%ct.request.functionname)
priority: WARNING
tags:
- cloud
- aws
- aws_lambda
source: aws_cloudtrail
- rule: Run Instances
desc: Detect launching of a specified number of instances.
condition:
ct.name="RunInstances" and not ct.error exists
output:
A number of instances have been launched.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
availability zone=%ct.request.availabilityzone,
subnet id=%ct.response.subnetid,
reservation id=%ct.response.reservationid)
priority: WARNING
tags:
- cloud
- aws
- aws_ec2
source: aws_cloudtrail
# Only instances launched on regions in this list are approved.
- list: approved_regions
items:
- us-east-0
- rule: Run Instances in Non-approved Region
desc: Detect launching of a specified number of instances in a non-approved region.
condition:
ct.name="RunInstances" and not ct.error exists and
not ct.region in (approved_regions)
output:
A number of instances have been launched in a non-approved region.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
availability zone=%ct.request.availabilityzone,
subnet id=%ct.response.subnetid,
reservation id=%ct.response.reservationid,
image id=%json.value[/responseElements/instancesSet/items/0/instanceId])
priority: WARNING
tags:
- cloud
- aws
- aws_ec2
source: aws_cloudtrail
- rule: Delete Bucket Encryption
desc: Detect deleting configuration to use encryption for bucket storage.
condition:
ct.name="DeleteBucketEncryption" and not ct.error exists
output:
A encryption configuration for a bucket has been deleted
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket=%s3.bucket)
priority: CRITICAL
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: Delete Bucket Public Access Block
desc: Detect deleting blocking public access to bucket.
condition:
ct.name="PutBucketPublicAccessBlock" and not ct.error exists and
json.value[/requestParameters/publicAccessBlock]='""' and
(json.value[/requestParameters/PublicAccessBlockConfiguration/RestrictPublicBuckets]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/BlockPublicPolicy]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/BlockPublicAcls]=false or
json.value[/requestParameters/PublicAccessBlockConfiguration/IgnorePublicAcls]=false)
output:
A pulic access block for a bucket has been deleted
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket=%s3.bucket)
priority: CRITICAL
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: List Buckets
desc: Detect listing of all S3 buckets.
condition:
ct.name="ListBuckets" and not ct.error exists
output:
A list of all S3 buckets has been requested.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
host=%ct.request.host)
priority: WARNING
enabled: false
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: Put Bucket ACL
desc: Detect setting the permissions on an existing bucket using access control lists.
condition:
ct.name="PutBucketAcl" and not ct.error exists
output:
The permissions on an existing bucket have been set using access control lists.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket name=%s3.bucket)
priority: WARNING
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: Put Bucket Policy
desc: Detect applying an Amazon S3 bucket policy to an Amazon S3 bucket.
condition:
ct.name="PutBucketPolicy" and not ct.error exists
output:
An Amazon S3 bucket policy has been applied to an Amazon S3 bucket.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
bucket name=%s3.bucket,
policy=%ct.request.policy)
priority: WARNING
tags:
- cloud
- aws
- aws_s3
source: aws_cloudtrail
- rule: CloudTrail Trail Created
desc: Detect creation of a new trail.
condition:
ct.name="CreateTrail" and not ct.error exists
output:
A new trail has been created.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
trail name=%ct.request.name)
priority: WARNING
tags:
- cloud
- aws
- aws_cloudtrail
source: aws_cloudtrail
- rule: CloudTrail Logging Disabled
desc: The CloudTrail logging has been disabled, this could be potentially malicious.
condition:
ct.name="StopLogging" and not ct.error exists
output:
The CloudTrail logging has been disabled.
(requesting user=%ct.user,
requesting IP=%ct.srcip,
AWS region=%ct.region,
resource name=%ct.request.name)
priority: WARNING
tags:
- cloud
- aws
- aws_cloudtrail
source: aws_cloudtrail

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");
@@ -29,13 +29,13 @@
# condition: (syscall.type=read and evt.dir=> and fd.type in (file, directory))
- macro: open_write
condition: evt.type in (open,openat,openat2) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0
condition: (evt.type=open or evt.type=openat) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0
- macro: open_read
condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='f' and fd.num>=0
condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='f' and fd.num>=0
- macro: open_directory
condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='d' and fd.num>=0
condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='d' and fd.num>=0
- macro: never_true
condition: (evt.num=0)
@@ -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,11 +216,11 @@
- 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.
# truncated at the sysdig level.
- list: package_mgmt_binaries
items: [rpm_binaries, deb_binaries, update-alternat, gem, pip, pip3, sane-utils.post, alternatives, chef-client, apk, snapd]
@@ -336,7 +333,7 @@
# for efficiency.
- macro: inbound_outbound
condition: >
((((evt.type in (accept,listen,connect) and evt.dir=<)) and
((((evt.type in (accept,listen,connect) and evt.dir=<)) or
(fd.typechar = 4 or fd.typechar = 6)) and
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
(evt.rawres >= 0 or evt.res = EINPROGRESS))
@@ -1758,6 +1755,7 @@
(container.image.repository startswith "602401143452.dkr.ecr" or
container.image.repository startswith "877085696533.dkr.ecr" or
container.image.repository startswith "800184023465.dkr.ecr" or
container.image.repository startswith "602401143452.dkr.ecr" or
container.image.repository startswith "918309763551.dkr.ecr" or
container.image.repository startswith "961992271922.dkr.ecr" or
container.image.repository startswith "590381155156.dkr.ecr" or
@@ -1829,7 +1827,6 @@
k8s.gcr.io/ip-masq-agent-amd64,
k8s.gcr.io/kube-proxy,
k8s.gcr.io/prometheus-to-sd,
public.ecr.aws/falcosecurity/falco,
quay.io/calico/node,
sysdig/sysdig,
sematext_images
@@ -1858,7 +1855,7 @@
- list: falco_sensitive_mount_images
items: [
docker.io/sysdig/sysdig, sysdig/sysdig,
docker.io/falcosecurity/falco, falcosecurity/falco, public.ecr.aws/falcosecurity/falco,
docker.io/falcosecurity/falco, falcosecurity/falco,
gcr.io/google_containers/hyperkube,
gcr.io/google_containers/kube-proxy, docker.io/calico/node,
docker.io/rook/toolbox, docker.io/cloudnativelabs/kube-router, docker.io/consul,
@@ -1874,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.
@@ -1896,13 +1906,12 @@
# For now, only considering a full mount of /etc as
# sensitive. Ideally, this would also consider all subdirectories
# below /etc as well, but the globbing mechanism
# below /etc as well, but the globbing mechanism used by sysdig
# doesn't allow exclusions of a full pattern, only single characters.
- macro: sensitive_mount
condition: (container.mount.dest[/proc*] != "N/A" or
container.mount.dest[/var/run/docker.sock] != "N/A" or
container.mount.dest[/var/run/crio/crio.sock] != "N/A" or
container.mount.dest[/run/containerd/containerd.sock] != "N/A" or
container.mount.dest[/var/lib/kubelet] != "N/A" or
container.mount.dest[/var/lib/kubelet/pki] != "N/A" or
container.mount.dest[/] != "N/A" or
@@ -2287,7 +2296,7 @@
desc: creating any files below /dev other than known programs that manage devices. Some rootkits hide files in /dev.
condition: >
fd.directory = /dev and
(evt.type = creat or (evt.type in (open,openat,openat2) and evt.arg.flags contains O_CREAT))
(evt.type = creat or ((evt.type = open or evt.type = openat) and evt.arg.flags contains O_CREAT))
and not proc.name in (dev_creation_binaries)
and not fd.name in (allowed_dev_files)
and not fd.name startswith /dev/tty
@@ -2355,8 +2364,7 @@
docker.io/sysdig/sysdig, docker.io/falcosecurity/falco,
sysdig/sysdig, falcosecurity/falco,
fluent/fluentd-kubernetes-daemonset, prom/prometheus,
ibm_cloud_containers,
public.ecr.aws/falcosecurity/falco)
ibm_cloud_containers)
or (k8s.ns.name = "kube-system"))
- macro: k8s_api_server
@@ -2698,18 +2706,8 @@
create_symlink and
(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
Symlinks created over senstivie files (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline target=%evt.arg.target linkpath=%evt.arg.linkpath parent_process=%proc.pname)
priority: NOTICE
tags: [file, mitre_exfiltration]
- list: miner_ports
@@ -2749,12 +2747,7 @@
"xmr-eu1.nanopool.org","xmr-eu2.nanopool.org",
"xmr-jp1.nanopool.org","xmr-us-east1.nanopool.org",
"xmr-us-west1.nanopool.org","xmr.crypto-pool.fr",
"xmr.pool.minergate.com", "rx.unmineable.com",
"ss.antpool.com","dash.antpool.com",
"eth.antpool.com","zec.antpool.com",
"xmc.antpool.com","btm.antpool.com",
"stratum-dash.antpool.com","stratum-xmc.antpool.com",
"stratum-btm.antpool.com"
"xmr.pool.minergate.com", "rx.unmineable.com"
]
- list: https_miner_domains
@@ -2771,12 +2764,7 @@
"stratum-ltc.antpool.com",
"stratum-zec.antpool.com",
"stratum.antpool.com",
"xmr.crypto-pool.fr",
"ss.antpool.com",
"stratum-dash.antpool.com",
"stratum-xmc.antpool.com",
"stratum-btm.antpool.com",
"btm.antpool.com"
"xmr.crypto-pool.fr"
]
- list: http_miner_domains
@@ -2805,7 +2793,7 @@
condition: (evt.type in (sendto, sendmsg) and evt.dir=< and (fd.net != "127.0.0.0/8" and not fd.snet in (rfc_1918_addresses)) and ((minerpool_http) or (minerpool_https) or (minerpool_other)))
- macro: trusted_images_query_miner_domain_dns
condition: (container.image.repository in (docker.io/falcosecurity/falco, falcosecurity/falco, public.ecr.aws/falcosecurity/falco))
condition: (container.image.repository in (docker.io/falcosecurity/falco, falcosecurity/falco))
append: false
# The rule is disabled by default.
@@ -2820,7 +2808,7 @@
- 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]
@@ -2956,7 +2944,7 @@
# The two Container Drift rules below will fire when a new executable is created in a container.
# There are two ways to create executables - file is created with execution permissions or permissions change of existing file.
# We will use a new filter, is_open_exec, to find all files creations with execution permission, and will trace all chmods in a container.
# We will use a new sysdig filter, is_open_exec, to find all files creations with execution permission, and will trace all chmods in a container.
# The use case we are targeting here is an attempt to execute code that was not shipped as part of a container (drift) -
# an activity that might be malicious or non-compliant.
# Two things to pay attention to:
@@ -2989,7 +2977,7 @@
- rule: Container Drift Detected (open+create)
desc: New executable created in a container due to open+create
condition: >
evt.type in (open,openat,openat2,creat) and
evt.type in (open,openat,creat) and
evt.is_open_exec=true and
container and
not runc_writing_exec_fifo and
@@ -3039,7 +3027,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]
@@ -3086,40 +3074,6 @@
priority: CRITICAL
tags: [syscall, mitre_defense_evasion]
- list: ingress_remote_file_copy_binaries
items: [wget]
- macro: ingress_remote_file_copy_procs
condition: (proc.name in (ingress_remote_file_copy_binaries))
# Users should overwrite this macro to specify conditions under which a
# Custom condition for use of ingress remote file copy tool in container
- macro: user_known_ingress_remote_file_copy_activities
condition: (never_true)
- macro: curl_download
condition: proc.name = curl and
(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 ")
- rule: Launch Ingress Remote File Copy Tools in Container
desc: Detect ingress remote file copy tools launched in container
condition: >
spawned_process and
container and
(ingress_remote_file_copy_procs or curl_download) and
not user_known_ingress_remote_file_copy_activities
output: >
Ingress remote file copy tool launched in container (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline parent_process=%proc.pname
container_id=%container.id container_name=%container.name image=%container.image.repository:%container.image.tag)
priority: NOTICE
tags: [network, process, mitre_command_and_control]
# 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

@@ -152,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.
@@ -315,31 +302,9 @@
items: []
- list: known_sa_list
items: [
coredns,
coredns-autoscaler,
cronjob-controller,
daemon-set-controller,
deployment-controller,
disruption-controller,
endpoint-controller,
endpointslice-controller,
endpointslicemirroring-controller,
generic-garbage-collector,
horizontal-pod-autoscaler,
job-controller,
namespace-controller,
node-controller,
persistent-volume-binder,
pod-garbage-collector,
pv-protection-controller,
pvc-protection-controller,
replicaset-controller,
resourcequota-controller,
root-ca-cert-publisher,
service-account-controller,
statefulset-controller
]
items: ["pod-garbage-collector","resourcequota-controller","cronjob-controller","generic-garbage-collector",
"daemon-set-controller","endpointslice-controller","deployment-controller", "replicaset-controller",
"endpoint-controller", "namespace-controller", "statefulset-controller", "disruption-controller"]
- macro: trusted_sa
condition: (ka.target.name in (known_sa_list, user_known_sa_list))

View File

@@ -154,32 +154,19 @@ load_kernel_module_compile() {
fi
# Try to compile using all the available gcc versions
for CURRENT_GCC in $(which gcc) $(ls "$(dirname "$(which gcc)")"/gcc-* | grep 'gcc-[0-9]\+' | sort -n -r -k 2 -t -); do
for CURRENT_GCC in $(which gcc) $(ls "$(dirname "$(which gcc)")"/gcc-* | grep 'gcc-[0-9]\+' | sort -r); do
echo "* Trying to dkms install ${DRIVER_NAME} module with GCC ${CURRENT_GCC}"
echo "#!/usr/bin/env bash" > /tmp/falco-dkms-make
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"
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
@@ -206,13 +193,8 @@ load_kernel_module_download() {
echo "* Trying to download a prebuilt ${DRIVER_NAME} module from ${URL}"
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 +238,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})"
@@ -265,7 +252,6 @@ load_kernel_module() {
if [ -f "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" ]; then
echo "* Found a prebuilt ${DRIVER_NAME} module at ${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}, loading it"
chcon -t modules_object_t "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true
insmod "${HOME}/.falco/${FALCO_KERNEL_MODULE_FILENAME}" && echo "* Success: ${DRIVER_NAME} module found and inserted"
exit $?
fi
@@ -278,13 +264,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
@@ -317,16 +296,16 @@ clean_kernel_module() {
return
fi
DRIVER_VERSIONS=$(dkms status -m "${DRIVER_NAME}" | cut -d',' -f1 | sed -e 's/^[[:space:]]*//')
DRIVER_VERSIONS=$(dkms status -m "${DRIVER_NAME}" | cut -d',' -f2 | sed -e 's/^[[:space:]]*//')
if [ -z "${DRIVER_VERSIONS}" ]; then
echo "* There is no ${DRIVER_NAME} module in dkms"
return
fi
for CURRENT_VER in ${DRIVER_VERSIONS}; do
if dkms remove "${CURRENT_VER}" --all 2>/dev/null; then
echo "* Removing ${CURRENT_VER} succeeded"
if dkms remove -m "${DRIVER_NAME}" -v "${CURRENT_VER}" --all 2>/dev/null; then
echo "* Removing ${DRIVER_NAME}/${CURRENT_VER} succeeded"
else
echo "* Removing ${CURRENT_VER} failed"
echo "* Removing ${DRIVER_NAME}/${CURRENT_VER} failed"
exit 1
fi
done
@@ -655,4 +634,4 @@ if [ -z "$source_only" ]; then
;;
esac
fi
fi
fi

View File

@@ -23,7 +23,7 @@ add_deb() {
cp -f $3 $1/$2
pushd $1/$2 > /dev/null
rm -f $(basename -- $3).asc
gpg --detach-sign --digest-algo SHA256 --armor $(basename -- $3)
gpg --detach-sign --armor $(basename -- $3)
popd > /dev/null
}
@@ -63,7 +63,7 @@ update_repo() {
${release_dir} > ${release_dir}/Release
# release signature
gpg --detach-sign --digest-algo SHA256 --armor ${release_dir}/Release
gpg --detach-sign --armor ${release_dir}/Release
rm -f ${release_dir}/Release.gpg
mv ${release_dir}/Release.asc ${release_dir}/Release.gpg
@@ -129,4 +129,4 @@ aws s3 sync ${tmp_repo_path}/dists ${s3_bucket_repo}/dists --delete --acl public
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}.asc
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/dists/*
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/dists/*

View File

@@ -22,7 +22,7 @@ add_rpm() {
cp -f $2 $1
pushd $1 > /dev/null
rm -f $(basename -- $2).asc
gpg --detach-sign --digest-algo SHA256 --armor $(basename -- $2)
gpg --detach-sign --armor $(basename -- $2)
popd > /dev/null
}
@@ -33,7 +33,7 @@ update_repo() {
pushd $1 > /dev/null
createrepo --update --no-database .
rm -f repodata/repomd.xml.asc
gpg --detach-sign --digest-algo SHA256 --armor repodata/repomd.xml
gpg --detach-sign --armor repodata/repomd.xml
popd > /dev/null
}
@@ -93,4 +93,4 @@ aws s3 sync ${tmp_repo_path}/repodata ${s3_bucket_repo}/repodata --delete --acl
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${package}
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${package}.asc
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/repodata/*
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/repodata/*

View File

@@ -19,7 +19,6 @@ ProtectSystem=full
ProtectKernelTunables=true
RestrictRealtime=true
RestrictAddressFamilies=~AF_PACKET
StandardOutput=null
[Install]
WantedBy=multi-user.target

View File

@@ -1,7 +1,4 @@
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)
add_dependencies(test-trace-files trace-files-base-scap trace-files-psp trace-files-k8s-audit)

View File

@@ -1,16 +0,0 @@
# This list is populated at cmake time, not build time
file(GLOB test_conf_files
"${CMAKE_CURRENT_SOURCE_DIR}/*.yaml")
foreach(conf_file_path ${test_conf_files})
get_filename_component(conf_file ${conf_file_path} NAME)
add_custom_target(test-conf-${conf_file} ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${conf_file})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${conf_file}
COMMAND sed -e s!BUILD_DIR!${CMAKE_BINARY_DIR}! < ${CMAKE_CURRENT_SOURCE_DIR}/${conf_file} > ${CMAKE_CURRENT_BINARY_DIR}/${conf_file}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${conf_file})
list(APPEND PLUGINS_CONF_FILES_TARGETS test-conf-${conf_file})
endforeach()
add_custom_target(conf-files-plugins ALL)
add_dependencies(conf-files-plugins ${PLUGINS_CONF_FILES_TARGETS})

View File

@@ -1,14 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: "BUILD_DIR/test/trace_files/plugins/alice_start_instances.json"
- name: json
library_path: BUILD_DIR/json-plugin-prefix/src/json-plugin/libjson.so
init_config: ""
# Optional
load_plugins: [cloudtrail, json]

View File

@@ -1,14 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: "BUILD_DIR/test/trace_files/plugins/alice_start_instances_bigevent.json"
- name: json
library_path: BUILD_DIR/json-plugin-prefix/src/json-plugin/libjson.so
init_config: ""
# Optional
load_plugins: [cloudtrail, json]

View File

@@ -1,14 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: ""
- name: test_extract_p1
library_path: BUILD_DIR/test/plugins/libtest_extract_p1.so
init_config: ""
# Optional
load_plugins: [cloudtrail, test_extract_p1]

View File

@@ -1,10 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: incompatible_plugin_api
library_path: BUILD_DIR/test/plugins/libtest_incompat_api.so
init_config: ""
# Optional
load_plugins: [incompatible_plugin_api]

View File

@@ -1,15 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: cloudtrail
library_path: BUILD_DIR/cloudtrail-plugin-prefix/src/cloudtrail-plugin/libcloudtrail.so
init_config: ""
open_params: "BUILD_DIR/test/trace_files/plugins/alice_start_instances.json"
- name: test_source
library_path: BUILD_DIR/test/plugins/libtest_source.so
init_config: ""
open_params: ""
# Optional
load_plugins: [cloudtrail, test_source]

View File

@@ -1,17 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: test_source
library_path: BUILD_DIR/test/plugins/libtest_source.so
init_config: ""
open_params: ""
- name: test_extract_p1
library_path: BUILD_DIR/test/plugins/libtest_extract_p1.so
init_config: ""
- name: test_extract_p2
library_path: BUILD_DIR/test/plugins/libtest_extract_p2.so
init_config: ""
# Optional
load_plugins: [test_source, test_extract_p1, test_extract_p2]

View File

@@ -1,10 +0,0 @@
stdout_output:
enabled: true
plugins:
- name: wrong_plugin_path
library_path: BUILD_DIR/test/plugins/wrong_plugin_path.so
init_config: ""
# Optional
load_plugins: [wrong_plugin_path]

View File

@@ -43,12 +43,6 @@ json_output: false
# (user=root ....") in the json output.
json_include_output_property: true
# When using json output, whether or not to include the "tags" property
# itself in the json output. If set to true, outputs caused by rules
# with no tags will have a "tags" field set to an empty array. If set to
# false, the "tags" field will not be included in the json output at all.
json_include_tags_property: true
# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: true

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash
#
# Copyright (C) 2021 The Falco Authors.
# Copyright (C) 2019 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,20 +16,10 @@
# limitations under the License.
#
- required_engine_version: 2
SUBJ_PID=$1
BENCHMARK=$2
VARIANT=$3
RESULTS_FILE=$4
CPU_INTERVAL=$5
- list: cat_binaries
items: [cat]
- list: cat_capable_binaries
items: [cat_binaries]
- macro: is_cat
condition: proc.name in (cat_capable_binaries)
- rule: open_from_cat
desc: A process named cat does an open
condition: evt.type=open and is_cat
output: "An open was seen (command=%proc.cmdline)"
priority: WARNING
tags: [filesystem, process, testing]
top -d $CPU_INTERVAL -b -p $SUBJ_PID | grep -E '(falco|sysdig|dragent|test_mm)' --line-buffered | awk -v benchmark=$BENCHMARK -v variant=$VARIANT '{printf("{\"time\": \"%s\", \"sample\": %d, \"benchmark\": \"%s\", \"variant\": \"%s\", \"cpu_usage\": %s},\n", strftime("%Y-%m-%d %H:%M:%S", systime(), 1), NR, benchmark, variant, $9); fflush();}' >> $RESULTS_FILE

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
@@ -634,12 +632,3 @@ trace_files: !mux
- ../rules/k8s_audit_rules.yaml
trace_file: trace_files/k8s_audit/fal_01_003.json
stderr_contains: 'Could not read k8s audit event line #1, "{"kind": 0}": Data not recognized as a k8s audit event, stopping'
json_pointer_correct_parse:
detect: True
detect_level: WARNING
rules_file:
- ./rules/k8s_audit/single_rule_with_json_pointer.yaml
detect_counts:
- json_pointer_example: 1
trace_file: trace_files/k8s_audit/create_nginx_pod_unprivileged.json

View File

@@ -82,7 +82,6 @@ class FalcoTest(Test):
self.exit_status = self.params.get('exit_status', '*', default=0)
self.should_detect = self.params.get('detect', '*', default=False)
self.check_detection_counts = self.params.get('check_detection_counts', '*', default=True)
self.trace_file = self.params.get('trace_file', '*', default='')
if self.trace_file and not os.path.isabs(self.trace_file):
@@ -91,11 +90,8 @@ class FalcoTest(Test):
self.json_output = self.params.get('json_output', '*', default=False)
self.json_include_output_property = self.params.get(
'json_include_output_property', '*', default=True)
self.json_include_tags_property = self.params.get(
'json_include_tags_property', '*', default=True)
self.all_events = self.params.get('all_events', '*', default=False)
self.priority = self.params.get('priority', '*', default='debug')
self.addl_cmdline_opts = self.params.get('addl_cmdline_opts', '*', default='')
self.rules_file = self.params.get(
'rules_file', '*', default=os.path.join(self.basedir, '../rules/falco_rules.yaml'))
@@ -132,7 +128,6 @@ class FalcoTest(Test):
self.conf_file = self.params.get(
'conf_file', '*', default=os.path.join(self.basedir, '../falco.yaml'))
self.conf_file = self.conf_file.replace("BUILD_DIR", build_dir)
if not os.path.isabs(self.conf_file):
self.conf_file = os.path.join(self.basedir, self.conf_file)
@@ -393,11 +388,10 @@ class FalcoTest(Test):
for line in res.stdout.decode("utf-8").splitlines():
if line.startswith('{'):
obj = json.loads(line)
attrs = ['time', 'rule', 'priority']
if self.json_include_output_property:
attrs.append('output')
if self.json_include_tags_property:
attrs.append('tags')
attrs = ['time', 'rule', 'priority', 'output']
else:
attrs = ['time', 'rule', 'priority']
for attr in attrs:
if not attr in obj:
self.fail(
@@ -415,15 +409,10 @@ class FalcoTest(Test):
else:
actual = open(output['actual']).read()
actual_cursor = actual
expected_lines = expected.splitlines()
for line in expected_lines:
pos = actual_cursor.find(line)
if pos < 0:
self.fail("Output '{}' does not strictly contains the expected content '{}'".format(
output['actual'], output['expected']))
return False
actual_cursor = actual_cursor[pos + len(line):]
if expected not in actual:
self.fail("Output '{}' does not strictly contains the expected content '{}'".format(
output['actual'], output['expected']))
return False
return True
@@ -620,9 +609,8 @@ class FalcoTest(Test):
self.log.debug("Converted Rules: {}".format(psp_rules))
# Run falco
cmd = '{} {} {} -c {} {} -o json_output={} -o json_include_output_property={} -o json_include_tags_property={} -o priority={} -v {}'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output,
self.json_include_output_property, self.json_include_tags_property, self.priority, self.addl_cmdline_opts)
cmd = '{} {} {} -c {} {} -o json_output={} -o json_include_output_property={} -o priority={} -v'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output, self.json_include_output_property, self.priority)
for tag in self.disable_tags:
cmd += ' -T {}'.format(tag)
@@ -653,13 +641,13 @@ class FalcoTest(Test):
self.fail("Stdout was not exactly {}".format(self.stderr_is))
for pattern in self.stderr_contains:
match = re.search(pattern, res.stderr.decode("utf-8"), re.DOTALL)
match = re.search(pattern, res.stderr.decode("utf-8"))
if match is None:
self.fail(
"Stderr of falco process did not contain content matching {}".format(pattern))
for pattern in self.stdout_contains:
match = re.search(pattern, res.stdout.decode("utf-8"), re.DOTALL)
match = re.search(pattern, res.stdout.decode("utf-8"))
if match is None:
self.fail("Stdout of falco process '{}' did not contain content matching {}".format(
res.stdout.decode("utf-8"), pattern))
@@ -687,7 +675,7 @@ class FalcoTest(Test):
self.check_rules_warnings(res)
if len(self.rules_events) > 0:
self.check_rules_events(res)
if len(self.validate_rules_file) == 0 and self.check_detection_counts:
if len(self.validate_rules_file) == 0:
self.check_detections(res)
if len(self.detect_counts) > 0:
self.check_detections_by_rule(res)

View File

@@ -32,10 +32,20 @@ trace_files: !mux
- leading_not
- not_equals_at_end
- not_at_end
- not_before_trailing_evttype
- not_equals_before_trailing_evttype
- not_equals_and_not
- not_equals_before_in
- not_before_in
- not_in_before_in
- leading_in_not_equals_before_evttype
- leading_in_not_equals_at_evttype
- not_with_evttypes
- not_with_evttypes_addl
- not_equals_before_evttype
- not_equals_before_in_evttype
- not_before_evttype
- not_before_evttype_using_in
rules_events:
- no_warnings: [execve]
- no_evttype: [all]
@@ -354,7 +364,7 @@ trace_files: !mux
exit_status: 1
stdout_is: |+
1 errors:
Appended rule must have exceptions or condition property
Rule must have exceptions or condition property
---
- rule: no condition rule
append: true
@@ -634,20 +644,6 @@ trace_files: !mux
rules_file:
- rules/single_rule_enabled_flag.yaml
trace_file: trace_files/cat_write.scap
disabled_rule_using_false_enabled_flag_only:
detect: False
rules_file:
- rules/disabled_rule_using_enabled_flag_only.yaml
trace_file: trace_files/cat_write.scap
enabled_rule_using_false_enabled_flag_only:
detect: True
detect_level: WARNING
rules_file:
- rules/enabled_rule_using_enabled_flag_only.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "Warning An open was seen"
disabled_and_enabled_rules_1:
exit_status: 1
@@ -692,7 +688,7 @@ trace_files: !mux
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule_with_tags.yaml
- rules/single_rule.yaml
conf_file: confs/stdout_output.yaml
trace_file: trace_files/cat_write.scap
time_iso_8601: true
@@ -725,7 +721,7 @@ trace_files: !mux
detect: True
detect_level: WARNING
rules_file:
- rules/single_rule_with_tags.yaml
- rules/single_rule.yaml
conf_file: confs/grpc_unix_socket.yaml
trace_file: trace_files/cat_write.scap
run_duration: 5
@@ -749,10 +745,6 @@ trace_files: !mux
# For the hostname, since we don't know that beforehand,
# only check the field presence
- "hostname: "
#tags
- "tags: \"filesystem\""
- "tags: \"process\""
- "tags: \"testing\""
detect_counts:
detect: True
@@ -1115,25 +1107,6 @@ trace_files: !mux
trace_file: trace_files/cat_write.scap
stdout_contains: "^(?!.*Warning An open of /dev/null was seen.*)"
json_output_no_tags_property:
json_output: True
json_include_tags_property: False
detect: True
detect_level: WARNING
rules_file:
- rules/rule_append.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "^(?!.*\"tags\":[ ]*\\[.*\\],.*)"
json_output_empty_tags_property:
json_output: True
detect: True
detect_level: WARNING
rules_file:
- rules/rule_append.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "^(.*\"tags\":[ ]*\\[\\],.*)"
in_operator_netmasks:
detect: True
detect_level: INFO
@@ -1146,8 +1119,6 @@ trace_files: !mux
detect_level: INFO
rules_file:
- rules/syscalls.yaml
rules_warning:
- detect_madvise
detect_counts:
- detect_madvise: 2
- detect_open: 2
@@ -1166,8 +1137,7 @@ trace_files: !mux
skip_unknown_noevt:
detect: False
rules_warning:
- Contains Unknown Event And Skipping
stdout_contains: Skipping rule "Contains Unknown Event And Skipping". contains unknown filter proc.nobody
rules_file:
- rules/skip_unknown_evt.yaml
trace_file: trace_files/cat_write.scap
@@ -1182,7 +1152,7 @@ trace_files: !mux
exit_status: 1
stderr_contains: |+
Could not load rules file.*skip_unknown_error.yaml: 1 errors:
Rule Contains Unknown Event And Not Skipping: error filter_check called with nonexistent field proc.nobody
rule "Contains Unknown Event And Not Skipping". contains unknown filter proc.nobody
---
- rule: Contains Unknown Event And Not Skipping
desc: Contains an unknown event
@@ -1199,7 +1169,7 @@ trace_files: !mux
exit_status: 1
stderr_contains: |+
Could not load rules file .*skip_unknown_unspec.yaml: 1 errors:
Rule Contains Unknown Event And Unspecified: error filter_check called with nonexistent field proc.nobody
rule "Contains Unknown Event And Unspecified". contains unknown filter proc.nobody
---
- rule: Contains Unknown Event And Unspecified
desc: Contains an unknown event

View File

@@ -177,18 +177,10 @@ trace_files: !mux
trace_file: trace_files/cat_write.scap
rule_exception_append_item_not_in_rule:
exit_status: 1
stdout_is: |+
1 errors:
Rule exception new item ex2: must have fields property with a list of fields
---
- rule: My Rule
exceptions:
- name: ex2
values:
- [apache, /tmp]
append: true
---
exit_status: 0
stderr_contains: |+
1 warnings:
Rule My Rule with append=true: no set of fields matching name ex2
validate_rules_file:
- rules/exceptions/append_item_not_in_rule.yaml
trace_file: trace_files/cat_write.scap
@@ -319,35 +311,4 @@ trace_files: !mux
- rules/exceptions/rule_exception_single_field_append.yaml
trace_file: trace_files/cat_write.scap
rule_exception_new_single_field_append:
detect: False
detect_level: WARNING
rules_file:
- rules/exceptions/rule_exception_new_single_field_append.yaml
trace_file: trace_files/cat_write.scap
rule_exception_new_second_field_append:
detect: False
detect_level: WARNING
rules_file:
- rules/exceptions/rule_exception_new_second_field_append.yaml
trace_file: trace_files/cat_write.scap
rule_exception_new_append_no_field:
exit_status: 1
stdout_is: |+
1 errors:
Rule exception new item proc_cmdline: must have fields property with a list of fields
---
- rule: Open From Cat
exceptions:
- name: proc_cmdline
comps: in
values:
- "cat /dev/null"
append: true
---
validate_rules_file:
- rules/exceptions/rule_exception_new_no_field_append.yaml
trace_file: trace_files/cat_write.scap

View File

@@ -1,106 +0,0 @@
#
# Copyright (C) 2021 The Falco Authors.
#
# This file is part of Falco.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
trace_files: !mux
list_plugins:
check_detection_counts: False
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
addl_cmdline_opts: --list-plugins
stdout_contains: "2 Plugins Loaded.*Name: cloudtrail.*Type: source plugin.*Name: json.*Type: extractor plugin"
list_plugin_fields:
check_detection_counts: False
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
addl_cmdline_opts: --list
stdout_contains: "ct.id"
detect_create_instance:
detect: True
detect_level: INFO
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
detect_counts:
- 'Cloudtrail Create Instance': 1
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances.yaml
detect_create_instance_bigevent:
detect: True
detect_level: INFO
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
detect_counts:
- 'Cloudtrail Create Instance': 1
conf_file: BUILD_DIR/test/confs/plugins/cloudtrail_json_create_instances_bigevent.yaml
multiple_source_plugins:
exit_status: 1
stderr_contains: "Runtime error: Can not load multiple source plugins. cloudtrail already loaded. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/multiple_source_plugins.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
incompatible_extract_sources:
exit_status: 1
stderr_contains: "Runtime error: Extractor plugin not compatible with event source aws_cloudtrail. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/incompatible_extract_sources.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
overlap_extract_sources:
exit_status: 1
stderr_contains: "Runtime error: Extractor plugins have overlapping compatible event source test_source. Exiting."
conf_file: BUILD_DIR/test/confs/plugins/overlap_extract_sources.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
incompat_plugin_api:
exit_status: 1
stderr_contains: "Unsupported plugin required api version 10000000.0.0"
conf_file: BUILD_DIR/test/confs/plugins/incompatible_plugin_api.yaml
rules_file:
- rules/plugins/cloudtrail_create_instances.yaml
incompat_plugin_rules_version:
exit_status: 1
stderr_contains: "Runtime error: Plugin cloudtrail version .* 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
wrong_plugin_path:
exit_status: 1
stderr_contains: "error loading plugin.*No such file or directory. Exiting"
conf_file: BUILD_DIR/test/confs/plugins/wrong_plugin_path.yaml
rules_file:
- rules/plugins/cloudtrail_incompat_plugin_version.yaml
no_plugins_unknown_source:
detect: False
rules_file:
- rules/plugins/cloudtrail_create_instances.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

@@ -111,17 +111,12 @@ traces: !mux
detect_counts:
- "Read sensitive file untrusted": 1
# This should *not* generate any falco alerts as of the changes in
# https://github.com/falcosecurity/libs/pull/94--the execve event in
# this trace file is PPME_SYSCALL_EXECVE_18, which was deprecated by
# PPME_SYSCALL_EXECVE_19 in 2018.
#
# This activity in this trace file overlaps with the activity in
# falco-event-generator.scap so the rule is still being tested.
run-shell-untrusted:
trace_file: traces-positive/run-shell-untrusted.scap
detect: False
detect: True
detect_level: DEBUG
detect_counts:
- "Run shell untrusted": 1
system-binaries-network-activity:
trace_file: traces-positive/system-binaries-network-activity.scap

View File

@@ -1,8 +1,8 @@
{"output":"2016-08-04T16:17:57.881781397+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881781397Z", "output_fields": {"evt.time.iso8601":1470327477881781397,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881785348+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881785348Z", "output_fields": {"evt.time.iso8601":1470327477881785348,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881796705+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881796705Z", "output_fields": {"evt.time.iso8601":1470327477881796705,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881799840+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.881799840Z", "output_fields": {"evt.time.iso8601":1470327477881799840,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882003104+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882003104Z", "output_fields": {"evt.time.iso8601":1470327477882003104,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882008208+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882008208Z", "output_fields": {"evt.time.iso8601":1470327477882008208,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882045694+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882045694Z", "output_fields": {"evt.time.iso8601":1470327477882045694,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882054739+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","source":"syscall","tags":["filesystem","process","testing"],"time":"2016-08-04T16:17:57.882054739Z", "output_fields": {"evt.time.iso8601":1470327477882054739,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881781397+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881781397Z", "output_fields": {"evt.time.iso8601":1470327477881781397,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881785348+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881785348Z", "output_fields": {"evt.time.iso8601":1470327477881785348,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881796705+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881796705Z", "output_fields": {"evt.time.iso8601":1470327477881796705,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.881799840+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.881799840Z", "output_fields": {"evt.time.iso8601":1470327477881799840,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882003104+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882003104Z", "output_fields": {"evt.time.iso8601":1470327477882003104,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882008208+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882008208Z", "output_fields": {"evt.time.iso8601":1470327477882008208,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882045694+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882045694Z", "output_fields": {"evt.time.iso8601":1470327477882045694,"proc.cmdline":"cat /dev/null"}}
{"output":"2016-08-04T16:17:57.882054739+0000: Warning An open was seen (command=cat /dev/null)","priority":"Warning","rule":"open_from_cat","time":"2016-08-04T16:17:57.882054739Z", "output_fields": {"evt.time.iso8601":1470327477882054739,"proc.cmdline":"cat /dev/null"}}

69
test/plot-live.r Normal file
View File

@@ -0,0 +1,69 @@
#
# 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.
#
require(jsonlite)
library(ggplot2)
library(GetoptLong)
initial.options <- commandArgs(trailingOnly = FALSE)
file.arg.name <- "--file="
script.name <- sub(file.arg.name, "", initial.options[grep(file.arg.name, initial.options)])
script.basename <- dirname(script.name)
if (substr(script.basename, 1, 1) != '/') {
script.basename = paste(getwd(), script.basename, sep='/')
}
results = paste(script.basename, "results.json", sep='/')
output = "./output.png"
metric = "cpu"
GetoptLong(
"results=s", "Path to results file",
"benchmark=s", "Benchmark from results file to graph",
"variant=s@", "Variant(s) to include in graph. Can be specified multiple times",
"output=s", "Output graph file",
"metric=s", "Metric to graph. Can be one of (cpu|drops)"
)
if (metric == "cpu") {
data_metric="cpu_usage"
yaxis_label="CPU Usage (%)"
title="Falco/Sysdig/Multimatch CPU Usage: %s"
} else if (metric == "drops") {
data_metric="drop_pct"
yaxis_label="Event Drops (%)"
title="Falco/Sysdig/Multimatch Event Drops: %s"
}
res <- fromJSON(results, flatten=TRUE)
res2 = res[res$benchmark == benchmark & res$variant %in% variant,]
plot <- ggplot(data=res2, aes(x=sample, y=get(data_metric), group=variant, colour=variant)) +
geom_line() +
ylab(yaxis_label) +
xlab("Time") +
ggtitle(sprintf(title, benchmark))
theme(legend.position=c(.2, .88));
print(paste("Writing graph to", output, sep=" "))
ggsave(file=output)

52
test/plot-traces.r Normal file
View File

@@ -0,0 +1,52 @@
#
# 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.
#
require(jsonlite)
library(ggplot2)
library(reshape)
res <- fromJSON("/home/mstemm/results.txt", flatten=TRUE)
plot <- ggplot(data=res, aes(x=config, y=elapsed.real)) +
geom_bar(stat = "summary", fun.y = "mean") +
coord_flip() +
facet_grid(shortfile ~ .) +
ylab("Wall Clock Time (sec)") +
xlab("Trace File/Program")
ggsave(file="/mnt/sf_mstemm/res-real.png")
plot <- ggplot(data=res, aes(x=config, y=elapsed.user)) +
geom_bar(stat = "summary", fun.y = "mean") +
coord_flip() +
facet_grid(shortfile ~ .) +
ylab("User Time (sec)") +
xlab("Trace File/Program")
ggsave(file="/mnt/sf_mstemm/res-user.png")
res2 <- melt(res, id.vars = c("config", "shortfile"), measure.vars = c("elapsed.sys", "elapsed.user"))
plot <- ggplot(data=res2, aes(x=config, y=value, fill=variable, order=variable)) +
geom_bar(stat = "summary", fun.y = "mean") +
coord_flip() +
facet_grid(shortfile ~ .) +
ylab("User/System Time (sec)") +
xlab("Trace File/Program")
ggsave(file="/mnt/sf_mstemm/res-sys-user.png")

View File

@@ -1,13 +0,0 @@
add_library(test_extract_p1 SHARED test_extract.cpp)
add_library(test_extract_p2 SHARED test_extract.cpp)
add_library(test_source SHARED test_source.cpp)
add_library(test_incompat_api SHARED incompat_api.cpp)
target_include_directories(
test_extract_p1 PUBLIC "${LIBSCAP_INCLUDE_DIRS}")
target_include_directories(
test_extract_p2 PUBLIC "${LIBSCAP_INCLUDE_DIRS}")
target_include_directories(
test_source PUBLIC "${LIBSCAP_INCLUDE_DIRS}")

View File

@@ -1,116 +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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <plugin_info.h>
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];
static const char *pl_desc = "Test Plugin For Regression Tests";
static const char *pl_contact = "github.com/falcosecurity/falco";
static const char *pl_version = "0.1.0";
static const char *pl_extract_sources = "[\"test_source\"]";
static const char *pl_fields = "[]";
// This struct represents the state of a plugin. Just has a placeholder string value.
typedef struct plugin_state
{
} plugin_state;
extern "C"
const char* plugin_get_required_api_version()
{
return pl_required_api_version;
}
extern "C"
uint32_t plugin_get_type()
{
return pl_type;
}
extern "C"
const char* plugin_get_name()
{
// Add a random-ish suffix to the end, as some tests load
// multiple copies of this plugin
snprintf(pl_name, sizeof(pl_name)-1, "%s%ld\n", pl_name_base, random());
return pl_name;
}
extern "C"
const char* plugin_get_description()
{
return pl_desc;
}
extern "C"
const char* plugin_get_contact()
{
return pl_contact;
}
extern "C"
const char* plugin_get_version()
{
return pl_version;
}
extern "C"
const char* plugin_get_extract_event_sources()
{
return pl_extract_sources;
}
extern "C"
const char* plugin_get_fields()
{
return pl_fields;
}
extern "C"
const char* plugin_get_last_error(ss_plugin_t* s)
{
return NULL;
}
extern "C"
ss_plugin_t* plugin_init(const char* config, int32_t* rc)
{
// Note: Using new/delete is okay, as long as the plugin
// framework is not deleting the memory.
plugin_state *ret = new plugin_state();
*rc = SS_PLUGIN_SUCCESS;
return ret;
}
extern "C"
void plugin_destroy(ss_plugin_t* s)
{
plugin_state *ps = (plugin_state *) s;
delete(ps);
}
extern "C"
int32_t plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event *evt, uint32_t num_fields, ss_plugin_extract_field *fields)
{
return SS_PLUGIN_SUCCESS;
}

View File

@@ -1,161 +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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <plugin_info.h>
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";
static const char *pl_desc = "Test Plugin For Regression Tests";
static const char *pl_contact = "github.com/falcosecurity/falco";
static const char *pl_version = "0.1.0";
static const char *pl_event_source = "test_source";
static const char *pl_fields = "[]";
// This struct represents the state of a plugin. Just has a placeholder string value.
typedef struct plugin_state
{
} plugin_state;
typedef struct instance_state
{
} instance_state;
extern "C"
const char* plugin_get_required_api_version()
{
return pl_required_api_version;
}
extern "C"
uint32_t plugin_get_type()
{
return pl_type;
}
extern "C"
uint32_t plugin_get_id()
{
return pl_id;
}
extern "C"
const char* plugin_get_name()
{
return pl_name;
}
extern "C"
const char* plugin_get_description()
{
return pl_desc;
}
extern "C"
const char* plugin_get_contact()
{
return pl_contact;
}
extern "C"
const char* plugin_get_version()
{
return pl_version;
}
extern "C"
const char* plugin_get_event_source()
{
return pl_event_source;
}
extern "C"
const char* plugin_get_fields()
{
return pl_fields;
}
extern "C"
const char* plugin_get_last_error(ss_plugin_t* s)
{
return NULL;
}
extern "C"
ss_plugin_t* plugin_init(const char* config, int32_t* rc)
{
// Note: Using new/delete is okay, as long as the plugin
// framework is not deleting the memory.
plugin_state *ret = new plugin_state();
*rc = SS_PLUGIN_SUCCESS;
return ret;
}
extern "C"
void plugin_destroy(ss_plugin_t* s)
{
plugin_state *ps = (plugin_state *) s;
delete(ps);
}
extern "C"
ss_instance_t* plugin_open(ss_plugin_t* s, const char* params, int32_t* rc)
{
// Note: Using new/delete is okay, as long as the plugin
// framework is not deleting the memory.
instance_state *ret = new instance_state();
*rc = SS_PLUGIN_SUCCESS;
return ret;
}
extern "C"
void plugin_close(ss_plugin_t* s, ss_instance_t* i)
{
instance_state *istate = (instance_state *) i;
delete(istate);
}
extern "C"
int32_t plugin_next_batch(ss_plugin_t* s, ss_instance_t* i, uint32_t *nevts, ss_plugin_event **evts)
{
return SS_PLUGIN_EOF;
}
// This plugin does not implement plugin_next_batch, due to the lower
// overhead of calling C functions from the plugin framework compared
// to calling Go functions.
extern "C"
const char *plugin_event_to_string(ss_plugin_t *s, const uint8_t *data, uint32_t datalen)
{
return "";
}
extern "C"
int32_t plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event *evt, uint32_t num_fields, ss_plugin_extract_field *fields)
{
return SS_PLUGIN_SUCCESS;
}

View File

@@ -1,24 +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.
#
- rule: open_from_cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen"
priority: WARNING
- rule: open_from_cat
enabled: false

View File

@@ -1,25 +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.
#
- rule: open_from_cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen"
priority: WARNING
enabled: false
- rule: open_from_cat
enabled: true

View File

@@ -1,31 +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.
#
- rule: Open From Cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen (command=%proc.cmdline)"
priority: WARNING
- rule: Open From Cat
exceptions:
- name: proc_cmdline
comps: in
values:
- "cat /dev/null"
append: true

View File

@@ -1,39 +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.
#
- rule: Open From Cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen (command=%proc.cmdline)"
exceptions:
- name: proc_cmdline
fields: proc.cmdline
comps: in
values:
- cat /dev/zero
priority: WARNING
- rule: Open From Cat
exceptions:
- name: proc_cmdline_2
fields: proc.cmdline
comps: in
values:
- "cat /dev/null"
append: true

View File

@@ -1,33 +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.
#
- rule: Open From Cat
desc: A process named cat does an open
condition: evt.type=open and proc.name=cat
output: "An open was seen (command=%proc.cmdline)"
priority: WARNING
- rule: Open From Cat
exceptions:
- name: proc_cmdline
fields: proc.cmdline
comps: in
values:
- "cat /dev/null"
append: true

View File

@@ -1,24 +1,2 @@
#
# 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.
#
- rule: no condition rule
desc: simpe rule
condition: evt.type=open
output: simple output
priority: WARNING
- rule: no condition rule
append: true

View File

@@ -1,23 +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.
#
- rule: json_pointer_example
desc: A rule example using JSON Pointer (RFC 6901)
condition: jevt.value[/annotations/authorization.k8s.io~1decision] exists and not (jevt.value[/a~1~0b] exists)
output: "JSON Pointer Test Alert"
priority: WARNING
source: k8s_audit

View File

@@ -1,6 +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])
priority: INFO
source: aws_cloudtrail

View File

@@ -1,10 +0,0 @@
- required_plugin_versions:
- name: cloudtrail
version: 100000.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])
priority: INFO
source: aws_cloudtrail

View File

@@ -16,10 +16,10 @@
#
- rule: my_rule
desc: A process named cat does an open
condition: (evt.type=open and fd.name=not-a-real-file)
condition: evt.type=open and fd.name=not-a-real-file
output: "An open of /dev/null was seen (command=%proc.cmdline)"
priority: WARNING
- rule: my_rule
append: true
condition: or (evt.type=open and fd.name=/dev/null)
condition: or fd.name=/dev/null

View File

@@ -15,7 +15,7 @@
# limitations under the License.
#
- macro: open_read
condition: evt.type in (open,openat,openat2) and evt.is_open_read=true and fd.typechar='f'
condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='f'
- rule: open_1
desc: open one

444
test/run_performance_tests.sh Executable file
View File

@@ -0,0 +1,444 @@
#!/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 -x
trap "cleanup; exit" SIGHUP SIGINT SIGTERM
TRACE_FILES_BASE_URL=${TRACE_FILES_BASE_URL:-"https://download.falco.org/fixtures/trace-files/"}
function download_trace_files() {
(mkdir -p $TRACEDIR && rm -rf $TRACEDIR/traces-perf && curl -fo $TRACEDIR/traces-perf.zip "${TRACE_FILES_BASE_URL}traces-perf.zip" && unzip -d $TRACEDIR $TRACEDIR/traces-perf.zip && rm -f $TRACEDIR/traces-perf.zip) || exit 1
}
function time_cmd() {
cmd="$1"
file="$2"
benchmark=`basename $file .scap`
echo -n "$benchmark: "
for i in `seq 1 5`; do
echo -n "$i "
time=`date --iso-8601=sec`
/usr/bin/time -a -o $RESULTS_FILE --format "{\"time\": \"$time\", \"benchmark\": \"$benchmark\", \"file\": \"$file\", \"variant\": \"$VARIANT\", \"elapsed\": {\"real\": %e, \"user\": %U, \"sys\": %S}}," $cmd >> $OUTPUT_FILE 2>&1
done
echo ""
}
function run_falco_on() {
file="$1"
if [ -z $RULES_FILE ]; then
RULES_FILE=$SOURCE/rules/falco_rules.yaml
fi
cmd="$ROOT/userspace/falco/falco -c $SOURCE/falco.yaml -r $SOURCE/rules/falco_rules.yaml --option=stdout_output.enabled=false -e $file -A"
time_cmd "$cmd" "$file"
}
function run_sysdig_on() {
file="$1"
cmd="$ROOT/userspace/sysdig/sysdig -N -z -r $file evt.type=none"
time_cmd "$cmd" "$file"
}
function write_agent_config() {
cat > $ROOT/userspace/dragent/dragent.yaml <<EOF
customerid: XXX
app_checks_enabled: false
log:
file_priority: info
console_priority: info
event_priority: info
jmx:
enabled: false
statsd:
enabled: false
collector: collector-staging.sysdigcloud.com
EOF
if [ $FALCO_AGENT == 1 ]; then
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
falco_engine:
enabled: true
rules_filename: /etc/falco_rules.yaml
sampling_multiplier: 0
EOF
else
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
falco_engine:
enabled: false
EOF
fi
if [ $AGENT_AUTODROP == 1 ]; then
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
autodrop:
enabled: true
EOF
else
cat >> $ROOT/userspace/dragent/dragent.yaml <<EOF
autodrop:
enabled: false
EOF
fi
cat $ROOT/userspace/dragent/dragent.yaml
}
function run_agent_on() {
file="$1"
write_agent_config
cmd="$ROOT/userspace/dragent/dragent -r $file"
time_cmd "$cmd" "$file"
}
function run_trace() {
if [ ! -e $TRACEDIR ]; then
download_trace_files
fi
trace_file="$1"
if [ $trace_file == "all" ]; then
files=($TRACEDIR/traces-perf/*.scap)
else
files=($TRACEDIR/traces-perf/$trace_file.scap)
fi
for file in "${files[@]}"; do
if [[ $ROOT == *"falco"* ]]; then
run_falco_on "$file"
elif [[ $ROOT == *"sysdig"* ]]; then
run_sysdig_on "$file"
else
run_agent_on "$file"
fi
done
}
function start_monitor_cpu_usage() {
echo " monitoring cpu usage for falcosecurity/falco program"
setsid bash `dirname $0`/cpu_monitor.sh $SUBJ_PID $live_test $VARIANT $RESULTS_FILE $CPU_INTERVAL &
CPU_PID=$!
sleep 5
}
function start_subject_prog() {
# Do a blocking sudo command now just to ensure we have a password
sudo bash -c ""
if [[ $ROOT == *"multimatch"* ]]; then
echo " starting test_mm..."
if [ -z $RULES_FILE ]; then
RULES_FILE=$SOURCE/../output/rules.yaml
fi
sudo FALCO_STATS_EXTRA_variant=$VARIANT FALCO_STATS_EXTRA_benchmark=$live_test $ROOT/test_mm -S $SOURCE/search_order.yaml -s $STATS_FILE -r $RULES_FILE > ./prog-output.txt 2>&1 &
elif [[ $ROOT == *"falco"* ]]; then
echo " starting falco..."
if [ -z $RULES_FILE ]; then
RULES_FILE=$SOURCE/rules/falco_rules.yaml
fi
sudo FALCO_STATS_EXTRA_variant=$VARIANT FALCO_STATS_EXTRA_benchmark=$live_test $ROOT/userspace/falco/falco -c $SOURCE/falco.yaml -s $STATS_FILE -r $RULES_FILE --option=stdout_output.enabled=false > ./prog-output.txt -A 2>&1 &
elif [[ $ROOT == *"sysdig"* ]]; then
echo " starting sysdig..."
sudo $ROOT/userspace/sysdig/sysdig -N -z evt.type=none &
else
echo " starting agent..."
write_agent_config
pushd $ROOT/userspace/dragent
sudo ./dragent > ./prog-output.txt 2>&1 &
popd
fi
SUDO_PID=$!
sleep 5
if [[ $ROOT == *"agent"* ]]; then
# The agent spawns several processes all below a main monitor
# process. We want the child with the lowest pid.
MON_PID=`ps -h -o pid --ppid $SUDO_PID`
SUBJ_PID=`ps -h -o pid --ppid $MON_PID | head -1`
else
SUBJ_PID=`ps -h -o pid --ppid $SUDO_PID`
fi
if [ -z $SUBJ_PID ]; then
echo "Could not find pid of subject program--did it start successfully? Not continuing."
exit 1
fi
}
function run_htop() {
screen -S htop-screen -d -m /usr/bin/htop -d2
sleep 90
screen -X -S htop-screen quit
}
function run_juttle_examples() {
pushd $SCRIPTDIR/../../juttle-engine/examples
docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml up -d
sleep 120
docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml stop
docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml rm -fv
popd
}
function run_kubernetes_demo() {
pushd $SCRIPTDIR/../../infrastructure/test-infrastructures/kubernetes-demo
sudo bash run-local.sh
sudo bash init.sh
sleep 600
docker stop $(docker ps -qa)
docker rm -fv $(docker ps -qa)
popd
}
function run_live_test() {
live_test="$1"
echo "Running live test $live_test"
case "$live_test" in
htop ) CPU_INTERVAL=2;;
* ) CPU_INTERVAL=10;;
esac
start_subject_prog
start_monitor_cpu_usage
echo " starting live program and waiting for it to finish"
case "$live_test" in
htop ) run_htop ;;
juttle-examples ) run_juttle_examples ;;
kube-demo ) run_kubernetes_demo ;;
* ) usage; cleanup; exit 1 ;;
esac
cleanup
}
function cleanup() {
if [ -n "$SUBJ_PID" ] ; then
echo " stopping falco/sysdig program $SUBJ_PID"
sudo kill $SUBJ_PID
fi
if [ -n "$CPU_PID" ] ; then
echo " stopping cpu monitor program $CPU_PID"
kill -- -$CPU_PID
fi
}
run_live_tests() {
test="$1"
if [ $test == "all" ]; then
tests="htop juttle-examples kube-demo"
else
tests=$test
fi
for test in $tests; do
run_live_test $test
done
}
function run_phoronix_test() {
live_test="$1"
case "$live_test" in
pts/aio-stress | pts/fs-mark | pts/iozone | pts/network-loopback | pts/nginx | pts/pybench | pts/redis | pts/sqlite | pts/unpack-linux ) CPU_INTERVAL=2;;
* ) CPU_INTERVAL=10;;
esac
echo "Running phoronix test $live_test"
start_subject_prog
start_monitor_cpu_usage
echo " starting phoronix test and waiting for it to finish"
TEST_RESULTS_NAME=$VARIANT FORCE_TIMES_TO_RUN=1 phoronix-test-suite default-run $live_test
cleanup
}
# To install and configure phoronix:
# (redhat instructions, adapt as necessary for ubuntu or other distros)
# - install phoronix: yum install phoronix-test-suite.noarch
# - install dependencies not handled by phoronix: yum install libaio-devel pcre-devel popt-devel glibc-static zlib-devel nc bc
# - fix trivial bugs in tests:
# - edit ~/.phoronix-test-suite/installed-tests/pts/network-loopback-1.0.1/network-loopback line "nc -d -l 9999 > /dev/null &" to "nc -d -l 9999 > /dev/null &"
# - edit ~/.phoronix-test-suite/test-profiles/pts/nginx-1.1.0/test-definition.xml line "<Arguments>-n 500000 -c 100 http://localhost:8088/test.html</Arguments>" to "<Arguments>-n 500000 -c 100 http://127.0.0.1:8088/test.html</Arguments>"
# - phoronix batch-install <test list below>
function run_phoronix_tests() {
test="$1"
if [ $test == "all" ]; then
tests="pts/aio-stress pts/apache pts/blogbench pts/compilebench pts/dbench pts/fio pts/fs-mark pts/iozone pts/network-loopback pts/nginx pts/pgbench pts/phpbench pts/postmark pts/pybench pts/redis pts/sqlite pts/unpack-linux"
else
tests=$test
fi
for test in $tests; do
run_phoronix_test $test
done
}
run_tests() {
IFS=':' read -ra PARTS <<< "$TEST"
case "${PARTS[0]}" in
trace ) run_trace "${PARTS[1]}" ;;
live ) run_live_tests "${PARTS[1]}" ;;
phoronix ) run_phoronix_tests "${PARTS[1]}" ;;
* ) usage; exit 1 ;;
esac
}
usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -h/--help: show this help"
echo " -v/--variant: a variant name to attach to this set of test results"
echo " -r/--root: root directory containing falco/sysdig binaries (i.e. where you ran 'cmake')"
echo " -s/--source: root directory containing falco/sysdig source code"
echo " -R/--results: append test results to this file"
echo " -S/--stats: append capture statistics to this file (only works for falco/test_mm)"
echo " -o/--output: append program output to this file"
echo " -U/--rules: path to rules file (only applicable for falco/test_mm)"
echo " -t/--test: test to run. Argument has the following format:"
echo " trace:<trace>: read the specified trace file."
echo " trace:all means run all traces"
echo " live:<live test>: run the specified live test."
echo " live:all means run all live tests."
echo " possible live tests:"
echo " live:htop: run htop -d2"
echo " live:kube-demo: run kubernetes demo from infrastructure repo"
echo " live:juttle-examples: run a juttle demo environment based on docker-compose"
echo " phoronix:<test>: run the specified phoronix test."
echo " if <test> is not 'all', it is passed directly to the command line of \"phoronix-test-suite run <test>\""
echo " if <test> is 'all', a built-in set of phoronix tests will be chosen and run"
echo " -T/--tracedir: Look for trace files in this directory. If doesn't exist, will download trace files from s3"
echo " -A/--agent-autodrop: When running an agent, whether or not to enable autodrop"
echo " -F/--falco-agent: When running an agent, whether or not to enable falco"
}
OPTS=`getopt -o hv:r:s:R:S:o:U:t:T: --long help,variant:,root:,source:,results:,stats:,output:,rules:,test:,tracedir:,agent-autodrop:,falco-agent: -n $0 -- "$@"`
if [ $? != 0 ]; then
echo "Exiting" >&2
exit 1
fi
eval set -- "$OPTS"
VARIANT="falco"
ROOT=`dirname $0`/../build
SOURCE=$ROOT
SCRIPTDIR=`dirname $0`
RESULTS_FILE=`dirname $0`/results.json
STATS_FILE=`dirname $0`/capture_stats.json
OUTPUT_FILE=`dirname $0`/program-output.txt
RULES_FILE=
TEST=trace:all
TRACEDIR=/tmp/falco-perf-traces.$USER
CPU_INTERVAL=10
AGENT_AUTODROP=1
FALCO_AGENT=1
while true; do
case "$1" in
-h | --help ) usage; exit 1;;
-v | --variant ) VARIANT="$2"; shift 2;;
-r | --root ) ROOT="$2"; shift 2;;
-s | --source ) SOURCE="$2"; shift 2;;
-R | --results ) RESULTS_FILE="$2"; shift 2;;
-S | --stats ) STATS_FILE="$2"; shift 2;;
-o | --output ) OUTPUT_FILE="$2"; shift 2;;
-U | --rules ) RULES_FILE="$2"; shift 2;;
-t | --test ) TEST="$2"; shift 2;;
-T | --tracedir ) TRACEDIR="$2"; shift 2;;
-A | --agent-autodrop ) AGENT_AUTODROP="$2"; shift 2;;
-F | --falco-agent ) FALCO_AGENT="$2"; shift 2;;
* ) break;;
esac
done
if [ -z $VARIANT ]; then
echo "A test variant name must be provided. Not continuing."
exit 1
fi
if [ -z $ROOT ]; then
echo "A root directory containing a falco/sysdig binary must be provided. Not continuing."
exit 1
fi
ROOT=`realpath $ROOT`
if [ -z $SOURCE ]; then
echo "A source directory containing falco/sysdig source code. Not continuing."
exit 1
fi
SOURCE=`realpath $SOURCE`
if [ -z $RESULTS_FILE ]; then
echo "An output file for test results must be provided. Not continuing."
exit 1
fi
if [ -z $STATS_FILE ]; then
echo "An output file for capture statistics must be provided. Not continuing."
exit 1
fi
if [ -z $OUTPUT_FILE ]; then
echo "An file for program output must be provided. Not continuing."
exit 1
fi
if [ -z $TEST ]; then
echo "A test must be provided. Not continuing."
exit 1
fi
run_tests

View File

@@ -20,7 +20,6 @@ set -euo pipefail
SCRIPT=$(readlink -f $0)
SCRIPTDIR=$(dirname "$SCRIPT")
SKIP_PACKAGES_TESTS=${SKIP_PACKAGES_TESTS:-false}
SKIP_PLUGINS_TESTS=${SKIP_PLUGINS_TESTS:-false}
TRACE_FILES_BASE_URL=${TRACE_FILES_BASE_URL:-"https://download.falco.org/fixtures/trace-files/"}
# Trace file tarballs are now versioned. Any time a substantial change
@@ -105,10 +104,6 @@ function run_tests() {
if [ "$SKIP_PACKAGES_TESTS" = false ] ; then
suites+=($SCRIPTDIR/falco_tests_package.yaml)
fi
if [ "$SKIP_PLUGINS_TESTS" = false ] ; then
suites+=($SCRIPTDIR/falco_tests_plugins.yaml)
fi
XUNIT_DIR="${OPT_BUILD_DIR}/integration-tests-xunit"
mkdir -p "${XUNIT_DIR}"

View File

@@ -1,6 +1,5 @@
add_subdirectory(k8s_audit)
add_subdirectory(psp)
add_subdirectory(plugins)
# Note: list of traces is created at cmake time, not build time
file(GLOB test_trace_files
@@ -17,4 +16,4 @@ foreach(trace_file_path ${test_trace_files})
endforeach()
add_custom_target(trace-files-base-scap ALL)
add_dependencies(trace-files-base-scap ${BASE_SCAP_TRACE_FILES_TARGETS})
add_dependencies(trace-files-base-scap ${BASE_SCAP_TRACE_FILES_TARGETS})

View File

@@ -1,16 +0,0 @@
# Note: list of traces is created at cmake time, not build time
file(GLOB test_trace_files
"${CMAKE_CURRENT_SOURCE_DIR}/*.json")
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,31 +0,0 @@
{"Records": [{
"eventVersion": "1.0",
"userIdentity": {
"type": "IAMUser",
"principalId": "EX_PRINCIPAL_ID",
"arn": "arn:aws:iam::123456789012:user/Alice",
"accessKeyId": "EXAMPLE_KEY_ID",
"accountId": "123456789012",
"userName": "Alice"
},
"eventTime": "2014-03-06T21:22:54Z",
"eventSource": "ec2.amazonaws.com",
"eventType": "AwsApiCall",
"eventName": "StartInstances",
"awsRegion": "us-east-2",
"sourceIPAddress": "205.251.233.176",
"userAgent": "ec2-api-tools 1.6.12.2",
"requestParameters": {"instancesSet": {"items": [{"instanceId": "i-ebeaf9e2"}]}},
"responseElements": {"instancesSet": {"items": [{
"instanceId": "i-ebeaf9e2",
"currentState": {
"code": 0,
"name": "pending"
},
"previousState": {
"code": 80,
"name": "stopped"
}
}]}}
}]}

File diff suppressed because one or more lines are too long

View File

@@ -15,25 +15,12 @@
# the License.
#
if(MINIMAL_BUILD)
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
falco/test_configuration.cpp
)
set(FALCO_TESTS_SOURCES test_base.cpp engine/test_token_bucket.cpp engine/test_rulesets.cpp engine/test_falco_utils.cpp)
else()
set(
FALCO_TESTS_SOURCES
test_base.cpp
engine/test_rulesets.cpp
engine/test_falco_utils.cpp
falco/test_configuration.cpp
falco/test_webserver.cpp
)
set(FALCO_TESTS_SOURCES test_base.cpp engine/test_token_bucket.cpp engine/test_rulesets.cpp engine/test_falco_utils.cpp falco/test_webserver.cpp)
endif()
set(FALCO_TESTED_LIBRARIES falco_engine ${YAMLCPP_LIB})
set(FALCO_TESTED_LIBRARIES falco_engine)
SET(FALCO_TESTS_ARGUMENTS "" CACHE STRING "Test arguments to pass to the Falco test suite")
@@ -59,7 +46,6 @@ if(FALCO_BUILD_TESTS)
PUBLIC "${CATCH2_INCLUDE}"
"${FAKEIT_INCLUDE}"
"${PROJECT_SOURCE_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/falco"
"${YAMLCPP_INCLUDE_DIR}"
"${PROJECT_SOURCE_DIR}/userspace/falco")
else()
@@ -68,7 +54,6 @@ if(FALCO_BUILD_TESTS)
PUBLIC "${CATCH2_INCLUDE}"
"${FAKEIT_INCLUDE}"
"${PROJECT_SOURCE_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/falco"
"${YAMLCPP_INCLUDE_DIR}"
"${CIVETWEB_INCLUDE_DIR}"
"${PROJECT_SOURCE_DIR}/userspace/falco")

View File

@@ -25,25 +25,15 @@ 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::shared_ptr<gen_event_filter> create_filter()
{
// The actual contents of the filters don't matter here.
sinsp_filter_compiler compiler(NULL, "evt.type=open");
sinsp_filter *f = compiler.compile();
std::shared_ptr<gen_event_filter> ret(f);
return ret;
}
static std::set<uint32_t> event_tags = {1};
TEST_CASE("Should enable/disable for exact match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("one_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -55,10 +45,10 @@ TEST_CASE("Should enable/disable for exact match w/ default ruleset", "[rulesets
TEST_CASE("Should enable/disable for exact match w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("one_rule", exact_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
@@ -74,10 +64,10 @@ TEST_CASE("Should enable/disable for exact match w/ specific ruleset", "[ruleset
TEST_CASE("Should not enable for exact match different rule name", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("some_other_rule", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
@@ -86,10 +76,10 @@ TEST_CASE("Should not enable for exact match different rule name", "[rulesets]")
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();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("one_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -101,10 +91,10 @@ TEST_CASE("Should enable/disable for exact match w/ substring and default rulese
TEST_CASE("Should not enable for substring w/ exact_match", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("one_", exact_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 0);
@@ -113,10 +103,10 @@ TEST_CASE("Should not enable for substring w/ exact_match", "[rulesets]")
TEST_CASE("Should enable/disable for prefix match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("one_", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -128,10 +118,10 @@ TEST_CASE("Should enable/disable for prefix match w/ default ruleset", "[ruleset
TEST_CASE("Should enable/disable for suffix match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("_rule", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -143,10 +133,10 @@ TEST_CASE("Should enable/disable for suffix match w/ default ruleset", "[ruleset
TEST_CASE("Should enable/disable for substring match w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("ne_ru", substring_match, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -158,10 +148,10 @@ TEST_CASE("Should enable/disable for substring match w/ default ruleset", "[rule
TEST_CASE("Should enable/disable for substring match w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable("ne_ru", substring_match, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
@@ -177,11 +167,11 @@ TEST_CASE("Should enable/disable for substring match w/ specific ruleset", "[rul
TEST_CASE("Should enable/disable for tags w/ default ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag"};
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -193,11 +183,11 @@ TEST_CASE("Should enable/disable for tags w/ default ruleset", "[rulesets]")
TEST_CASE("Should enable/disable for tags w/ specific ruleset", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag"};
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable_tags(want_tags, enabled, non_default_ruleset);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 1);
@@ -213,11 +203,11 @@ TEST_CASE("Should enable/disable for tags w/ specific ruleset", "[rulesets]")
TEST_CASE("Should not enable for different tags", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_different_tag"};
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(non_default_ruleset) == 0);
@@ -226,11 +216,11 @@ TEST_CASE("Should not enable for different tags", "[rulesets]")
TEST_CASE("Should enable/disable for overlapping tags", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> filter = create_filter();
gen_event_filter *filter = new gen_event_filter();
string rule_name = "one_rule";
std::set<std::string> want_tags = {"some_tag", "some_different_tag"};
r.add(rule_name, tags, filter);
r.add(rule_name, tags, event_tags, filter);
r.enable_tags(want_tags, enabled);
REQUIRE(r.num_rules_for_ruleset(default_ruleset) == 1);
@@ -242,15 +232,15 @@ TEST_CASE("Should enable/disable for overlapping tags", "[rulesets]")
TEST_CASE("Should enable/disable for incremental adding tags", "[rulesets]")
{
falco_ruleset r;
std::shared_ptr<gen_event_filter> rule1_filter = create_filter();
gen_event_filter *rule1_filter = new gen_event_filter();
string rule1_name = "one_rule";
std::set<std::string> rule1_tags = {"rule1_tag"};
r.add(rule1_name, rule1_tags, rule1_filter);
r.add(rule1_name, rule1_tags, event_tags, rule1_filter);
std::shared_ptr<gen_event_filter> rule2_filter = create_filter();
gen_event_filter *rule2_filter = new gen_event_filter();
string rule2_name = "two_rule";
std::set<std::string> rule2_tags = {"rule2_tag"};
r.add(rule2_name, rule2_tags, rule2_filter);
r.add(rule2_name, rule2_tags, event_tags, rule2_filter);
std::set<std::string> want_tags;

View File

@@ -0,0 +1,83 @@
/*
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 <memory>
#include "token_bucket.h"
#include <catch.hpp>
using namespace Catch::literals;
TEST_CASE("token bucket default ctor", "[token_bucket]")
{
auto tb = std::make_shared<token_bucket>();
REQUIRE(tb->get_tokens() == 1);
SECTION("initialising with specific time, rate 2 tokens/sec")
{
auto max = 2.0;
uint64_t now = 1;
tb->init(1.0, max, now);
REQUIRE(tb->get_last_seen() == now);
REQUIRE(tb->get_tokens() == max);
}
}
TEST_CASE("token bucket ctor with custom timer", "[token_bucket]")
{
auto t = []() -> uint64_t { return 22; };
auto tb = std::make_shared<token_bucket>(t);
REQUIRE(tb->get_tokens() == 1);
REQUIRE(tb->get_last_seen() == 22);
}
TEST_CASE("token bucket with 2 tokens/sec rate, max 10 tokens", "[token_bucket]")
{
auto tb = std::make_shared<token_bucket>();
tb->init(2.0, 10, 1);
SECTION("claiming 5 tokens")
{
bool claimed = tb->claim(5, 1000000001);
REQUIRE(tb->get_last_seen() == 1000000001);
REQUIRE(tb->get_tokens() == 5.0_a);
REQUIRE(claimed);
SECTION("claiming all the 7 remaining tokens")
{
bool claimed = tb->claim(7, 2000000001);
REQUIRE(tb->get_last_seen() == 2000000001);
REQUIRE(tb->get_tokens() == 0.0_a);
REQUIRE(claimed);
SECTION("claiming 1 token more than the 2 available fails")
{
bool claimed = tb->claim(3, 3000000001);
REQUIRE(tb->get_last_seen() == 3000000001);
REQUIRE(tb->get_tokens() == 2.0_a);
REQUIRE_FALSE(claimed);
}
}
}
}
TEST_CASE("token bucket default initialization", "[token_bucket]")
{
token_bucket tb;
REQUIRE(tb.get_tokens() == 1);
}

View File

@@ -1,106 +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 "configuration.h"
#include <catch.hpp>
string sample_yaml =
"base_value:\n"
" id: 1\n"
" name: 'sample_name'\n"
" subvalue:\n"
" subvalue2:\n"
" boolean: true\n"
"base_value_2:\n"
" sample_list:\n"
" - elem1\n"
" - elem2\n"
" - elem3\n"
;
TEST_CASE("configuration must load YAML data", "[configuration]")
{
yaml_configuration conf;
SECTION("broken YAML")
{
string sample_broken_yaml = sample_yaml + " / bad_symbol";
REQUIRE_THROWS(conf.load_from_string(sample_broken_yaml));
}
SECTION("valid YAML")
{
REQUIRE_NOTHROW(conf.load_from_string(sample_yaml));
}
SECTION("clearing and reloading")
{
conf.load_from_string(sample_yaml);
REQUIRE(conf.is_defined("base_value") == true);
conf.clear();
REQUIRE(conf.is_defined("base_value") == false);
conf.load_from_string(sample_yaml);
REQUIRE(conf.is_defined("base_value") == true);
}
}
TEST_CASE("configuration must read YAML fields", "[configuration]")
{
yaml_configuration conf;
conf.load_from_string(sample_yaml);
SECTION("base level")
{
REQUIRE(conf.is_defined("base_value") == true);
REQUIRE(conf.is_defined("base_value_2") == true);
REQUIRE(conf.is_defined("unknown_base_value") == false);
}
SECTION("arbitrary depth nesting")
{
REQUIRE(conf.get_scalar<int>("base_value.id", -1) == 1);
REQUIRE(conf.get_scalar<string>("base_value.name", "none") == "sample_name");
REQUIRE(conf.get_scalar<bool>("base_value.subvalue.subvalue2.boolean", false) == true);
}
SECTION("list field elements")
{
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[0]", "none") == "elem1");
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[1]", "none") == "elem2");
REQUIRE(conf.get_scalar<string>("base_value_2.sample_list[2]", "none") == "elem3");
}
SECTION("sequence")
{
vector<string> seq;
conf.get_sequence(seq, "base_value_2.sample_list");
REQUIRE(seq.size() == 3);
REQUIRE(seq[0] == "elem1");
REQUIRE(seq[1] == "elem2");
REQUIRE(seq[2] == "elem3");
}
}
TEST_CASE("configuration must modify YAML fields", "[configuration]")
{
string key = "base_value.subvalue.subvalue2.boolean";
yaml_configuration conf;
conf.load_from_string(sample_yaml);
REQUIRE(conf.get_scalar<bool>(key, false) == true);
conf.set_scalar<bool>(key, false);
REQUIRE(conf.get_scalar<bool>(key, true) == false);
conf.set_scalar<bool>(key, true);
REQUIRE(conf.get_scalar<bool>(key, false) == true);
}

View File

@@ -10,8 +10,6 @@
# "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
@@ -19,6 +17,7 @@ set(FALCO_ENGINE_SOURCE_FILES
falco_utils.cpp
json_evt.cpp
ruleset.cpp
token_bucket.cpp
formats.cpp)
add_library(falco_engine STATIC ${FALCO_ENGINE_SOURCE_FILES})
@@ -38,8 +37,7 @@ if(MINIMAL_BUILD)
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/engine/lua")
"${PROJECT_BINARY_DIR}/userspace/engine")
else()
target_include_directories(
falco_engine
@@ -51,8 +49,26 @@ else()
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/userspace/engine"
"${PROJECT_BINARY_DIR}/userspace/engine/lua")
"${PROJECT_BINARY_DIR}/userspace/engine")
endif()
target_link_libraries(falco_engine "${FALCO_SINSP_LIBRARY}" "${LPEG_LIB}" "${LYAML_LIB}" "${LIBYAML_LIB}" luafiles)
target_link_libraries(falco_engine "${FALCO_SINSP_LIBRARY}" "${LPEG_LIB}" "${LYAML_LIB}" "${LIBYAML_LIB}")
configure_file(config_falco_engine.h.in config_falco_engine.h)
if(DEFINED FALCO_COMPONENT)
install(
DIRECTORY lua
DESTINATION "${FALCO_SHARE_DIR}"
COMPONENT "${FALCO_COMPONENT}"
FILES_MATCHING
PATTERN *.lua)
else()
install(
DIRECTORY lua
DESTINATION "${FALCO_SHARE_DIR}"
FILES_MATCHING
PATTERN *.lua)
endif()
add_subdirectory(embeddable)

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2021 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,15 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include <string.h>
#include <stdlib.h>
#pragma once
// Don't need any function other than plugin_get_required_api_version,
// plugin load will fail after that.
static const char *pl_required_api_version = "10000000.0.0";
extern "C"
const char* plugin_get_required_api_version()
{
return pl_required_api_version;
}
#define FALCO_ENGINE_LUA_DIR "${FALCO_ABSOLUTE_SHARE_DIR}/lua/"
#define FALCO_ENGINE_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/userspace/engine/lua/"

View File

@@ -0,0 +1,43 @@
#
# 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.
set(FALCO_ENGINE_EMBEDDABLE_SOURCE_FILES
falco_engine_embeddable.cpp)
set(
FALCO_LIBRARIES
falco_engine
sinsp
"${LIBYAML_LIB}"
"${YAMLCPP_LIB}"
)
add_library(falco_engine_embeddable SHARED ${FALCO_ENGINE_EMBEDDABLE_SOURCE_FILES})
add_dependencies(falco_engine_embeddable falco_engine)
target_include_directories(
falco_engine_embeddable
PUBLIC
"${PROJECT_SOURCE_DIR}/userspace/engine"
"${LUAJIT_INCLUDE}"
"${NJSON_INCLUDE}"
"${TBB_INCLUDE_DIR}"
"${STRING_VIEW_LITE_INCLUDE}"
"${LIBSCAP_INCLUDE_DIRS}"
"${LIBSINSP_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/userspace/engine")
target_link_libraries(falco_engine_embeddable ${FALCO_LIBRARIES})
#add_custom_target(example ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/example)
#add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/example COMMAND go build ${CMAKE_CURRENT_SOURCE_DIR}/example.go -o ${CMAKE_CURRENT_BINARY_DIR}/example)

View File

@@ -0,0 +1,102 @@
package main
//#cgo CFLAGS: -I../
//#cgo LDFLAGS: -L/home/mstemm/work/falco-build/userspace/engine/embeddable -lfalco_engine_embeddable -Wl,-rpath=/home/mstemm/work/falco-build/userspace/engine/embeddable
/*
#include "stdio.h"
#include "falco_engine_embeddable.h"
int open_engine(void **engine, void *rules_content)
{
int32_t rc;
*engine = falco_engine_embed_init(&rc);
if (rc != 0)
{
return rc;
}
char *errstr;
rc = falco_engine_embed_load_rules_content(*engine, (const char *) rules_content, &errstr);
if (rc != 0)
{
fprintf(stderr, "%s", errstr);
return rc;
}
rc = falco_engine_embed_open(*engine, &errstr);
if (rc != 0)
{
fprintf(stderr, "%s", errstr);
return rc;
}
return rc;
}
int next_result(void *engine, char **output)
{
int32_t rc;
falco_engine_embed_result *res;
char *errstr;
rc = falco_engine_embed_next_result(engine, &res, &errstr);
if (rc != 0)
{
fprintf(stderr, "NEXT ERROR %s", errstr);
return rc;
}
*output = res->output_str;
return rc;
}
*/
import "C"
import (
"fmt"
"io/ioutil"
"os"
"unsafe"
)
func doMain(rules_filename string) int {
rules_content, err := ioutil.ReadFile(rules_filename)
if err != nil {
fmt.Printf("Could not open rules file %s: %v", rules_filename, err)
return 1
}
var handle unsafe.Pointer
rc := C.open_engine(&handle, C.CBytes(rules_content))
if rc != 0 {
fmt.Printf("Could not open falco engine")
return 1
}
for true {
var output *C.char
rc := C.next_result(handle, &output)
if rc != 0 {
fmt.Printf("Could not get next result")
return 1
}
fmt.Printf("GOT RESULT %s\n", C.GoString(output))
}
return 0
}
func main() {
os.Exit(doMain(os.Args[1]))
}

View File

@@ -0,0 +1,6 @@
module github.com/falcosecurity/falco/embedded/example
go 1.16
require (
)

View File

@@ -0,0 +1,356 @@
/*
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 <memory>
#include <atomic>
#include <sinsp.h>
#include <event.h>
#include <falco_engine.h>
#include "falco_engine_embeddable.h"
using namespace std;
class falco_engine_embed_int {
public:
falco_engine_embed_int();
virtual ~falco_engine_embed_int();
bool load_rules_content(const char *rules_content, string &err);
bool is_open();
bool open(string &err);
bool close(string &err);
falco_engine_embed_rc next_result(falco_engine_embed_result **result, string &err);
private:
falco_engine_embed_result *rule_result_to_embed_result(sinsp_evt *ev,
unique_ptr<falco_engine::rule_result> &res);
static void add_output_pair(const string &field, const string &val,
char **&fields, char **&vals,
uint32_t &len);
unique_ptr<sinsp_evt_formatter_cache> m_formatters;
bool m_open;
unique_ptr<sinsp> m_inspector;
unique_ptr<falco_engine> m_falco_engine;
atomic<bool> m_shutdown;
};
falco_engine_embed_int::falco_engine_embed_int()
: m_open(false),
m_shutdown(false)
{
m_inspector.reset(new sinsp());
m_falco_engine.reset(new falco_engine());
m_falco_engine->set_inspector(m_inspector.get());
m_formatters.reset(new sinsp_evt_formatter_cache(m_inspector.get()));
}
falco_engine_embed_int::~falco_engine_embed_int()
{
}
bool falco_engine_embed_int::load_rules_content(const char *rules_content, string &err)
{
bool verbose = false;
bool all_events = true;
try {
m_falco_engine->load_rules(string(rules_content), verbose, all_events);
}
catch(falco_exception &e)
{
err = e.what();
return false;
}
return true;
}
bool falco_engine_embed_int::is_open()
{
return m_open;
}
bool falco_engine_embed_int::open(string &err)
{
try {
m_inspector->open();
}
catch(exception &e)
{
err = e.what();
return false;
}
m_open = true;
return true;
}
bool falco_engine_embed_int::close(string &err)
{
m_shutdown = true;
m_open = false;
return true;
}
falco_engine_embed_rc falco_engine_embed_int::next_result(falco_engine_embed_result **result, string &err)
{
*result = NULL;
while(!m_shutdown)
{
sinsp_evt* ev;
int32_t rc = m_inspector->next(&ev);
if (rc == SCAP_TIMEOUT)
{
continue;
}
else if (rc == SCAP_EOF)
{
break;
}
else if (rc != SCAP_SUCCESS)
{
err = m_inspector->getlasterr();
return FE_EMB_RC_ERROR;
}
if(!ev->simple_consumer_consider())
{
continue;
}
unique_ptr<falco_engine::rule_result> res = m_falco_engine->process_sinsp_event(ev);
if(!res)
{
continue;
}
*result = rule_result_to_embed_result(ev, res);
return FE_EMB_RC_OK;
}
// Can only get here if shut down/eof.
return FE_EMB_RC_EOF;
}
falco_engine_embed_result * falco_engine_embed_int::rule_result_to_embed_result(sinsp_evt *ev,
unique_ptr<falco_engine::rule_result> &res)
{
falco_engine_embed_result *result;
result = (falco_engine_embed_result *) malloc(sizeof(falco_engine_embed_result));
result->rule = strdup(res->rule.c_str());
result->event_source = strdup(res->source.c_str());
result->priority_num = res->priority_num;
// Copy output format string without resolving fields.
result->output_format_str = strdup(res->format.c_str());
// Resolve output format string into resolved output
string output;
m_formatters->tostring(ev, res->format, &output);
result->output_str = strdup(output.c_str());
result->output_fields = NULL;
result->output_values = NULL;
result->num_output_values = 0;
map<string, string> rule_output_fields;
m_formatters->resolve_tokens(ev, res->format, rule_output_fields);
for(auto &pair : rule_output_fields)
{
add_output_pair(pair.first, pair.second,
result->output_fields, result->output_values,
result->num_output_values);
}
// Preceding * makes the formatting permissive (not ending at first empty value)
std::string exformat = "*";
for (const auto& exfield : res->exception_fields)
{
exformat += " %" + exfield;
}
map<string, string> exception_output_fields;
m_formatters->resolve_tokens(ev, exformat, exception_output_fields);
for(auto &pair : exception_output_fields)
{
add_output_pair(pair.first, pair.second,
result->output_fields, result->output_values,
result->num_output_values);
}
return result;
}
void falco_engine_embed_int::add_output_pair(const string &field, const string &val,
char **&fields, char **&vals,
uint32_t &len)
{
len++;
fields = (char **) realloc(fields, len*sizeof(char *));
vals = (char **) realloc(vals, len*sizeof(char *));
fields[len-1] = strdup(field.c_str());
vals[len-1] = strdup(val.c_str());
}
static const char *FALCO_ENGINE_EMBED_VERSION = "1.0.0";
char *falco_engine_embed_get_version()
{
return strdup(FALCO_ENGINE_EMBED_VERSION);
}
void falco_engine_embed_free_result(falco_engine_embed_result *result)
{
free(result->rule);
free(result->event_source);
free(result->output_format_str);
free(result->output_str);
for(int32_t i; i < result->num_output_values; i++)
{
free(result->output_fields[i]);
free(result->output_values[i]);
}
free(result->output_fields);
free(result->output_values);
free(result);
}
falco_engine_embed_t* falco_engine_embed_init(int32_t *rc)
{
falco_engine_embed_int *eengine = new falco_engine_embed_int();
*rc = FE_EMB_RC_OK;
return eengine;
}
int32_t falco_engine_embed_destroy(falco_engine_embed_t *engine, char *errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
if(eengine->is_open())
{
errstr = strdup("Engine is open--must call close() first");
return FE_EMB_RC_ERROR;
}
delete(eengine);
return FE_EMB_RC_OK;
}
int32_t falco_engine_embed_load_plugin(falco_engine_embed_t *engine,
const char *path,
const char* init_config,
const char* open_params,
char **errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
// XXX/mstemm fill in
return FE_EMB_RC_OK;
}
int32_t falco_engine_embed_load_rules_content(falco_engine_embed_t *engine,
const char *rules_content,
char **errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
std::string err;
if (!eengine->load_rules_content(rules_content, err))
{
*errstr = strdup(err.c_str());
return FE_EMB_RC_ERROR;
}
return FE_EMB_RC_OK;
}
int32_t falco_engine_embed_enable_source(falco_engine_embed_t *engine,
int32_t source,
bool enabled,
char **errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
// XXX/mstemm fill in
return FE_EMB_RC_OK;
}
int32_t falco_engine_embed_open(falco_engine_embed_t *engine,
char **errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
std::string err;
if (!eengine->open(err))
{
*errstr = strdup(err.c_str());
return FE_EMB_RC_ERROR;
}
return FE_EMB_RC_OK;
}
int32_t falco_engine_embed_close(falco_engine_embed_t *engine,
char **errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
std::string err;
if (!eengine->close(err))
{
*errstr = strdup(err.c_str());
return FE_EMB_RC_ERROR;
}
return FE_EMB_RC_OK;
}
int32_t falco_engine_embed_next_result(falco_engine_embed_t *engine,
falco_engine_embed_result **result,
char **errstr)
{
falco_engine_embed_int *eengine = (falco_engine_embed_int *) engine;
std::string err;
falco_engine_embed_rc rc;
rc = eengine->next_result(result, err);
if(rc == FE_EMB_RC_ERROR)
{
*errstr = strdup(err.c_str());
}
return rc;
}

View File

@@ -0,0 +1,268 @@
/*
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.
*/
/*
This header file provides a C-only interface to the falco engine,
suitable for embedding in other programs as a shared library. This
interface handles:
- Loading Rules Content
- Enabling/Disabling syscall/k8s_audit event sources.
- Loading and configuring source/extractor plugins
- Starting/Stopping the event processing loop.
After setup, the main interface involves receiving "results" when
syscall/k8s_audit/plugin events match rules.
This interface does not provide as many features as the c++
falco_engine interface, such as interfaces to list rules, segregate
rules by "ruleset", enabling/disabling specific rules etc.
Output handling (e.g. routing alerts to files, stdout, webhook,
slack, etc) is not covered by this interface. After receiving a
result, a program could use a program like falcosidekick for a rich
set of output handling methods.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* A handle to an embeddable falco engine */
typedef void falco_engine_embed_t;
/* Defined return values from API functions. */
enum falco_engine_embed_rc
{
/* No Error */
FE_EMB_RC_OK = 0,
FE_EMB_RC_ERROR = 1,
FE_EMB_RC_EOF = 2,
};
/* Defined event sources. */
enum falco_engine_embed_evt_source
{
FE_EMB_SRC_NONE = 0,
FE_EMB_SRC_SYSCALL = 1,
FE_EMB_K8S_AUDIT = 2,
FE_EMB_PLUGINS = 3, // This includes any event from any plugin
};
/* Represents a result (e.g. an event matching a falco rule)
When returned by a call to next_result(), the struct, as well as
every allocated char * within the struct, is allocated via a call
to malloc() and must be freed via a call to free().
*/
typedef struct falco_engine_embed_result
{
// The rule that matched the event
char *rule;
// The event source of the event that matched the rule
char *event_source;
// An int containing a falco_common::priority_type value of
// the priority of the matching rule.
int32_t priority_num;
// A copy of the rule's output string, *without* any
// fields (e.g. %proc.name, ...) resolved to values.
char *output_format_str;
// An output string, starting with the rule's output string
// with all fields resolved to values.
char *output_str;
// An allocated array of allocated field names from the output
// string. Additional fields + values may be included in
// addition to those in the output string, to aid in
// debugging. Item i in this array maps to item i in
// output_values.
char **output_fields;
// An allocated array of allocated field values from the
// output string. Additional fields + values may be included in
// addition to those in the output string, to aid in
// debugging. Item i in this array maps to item i in
// output_fields.
char **output_values;
// The length of output_fields/output_values
uint32_t num_output_values;
} falco_engine_embed_result;
/* A utility function to free a falco_engine_embed_result struct and
* its allocated strings returned by a call to next_result() */
void falco_engine_embed_free_result(falco_engine_embed_result *result);
// Interface to interact with an embeddable falco engine.
// NOTE: For all functions below that return a char *, the memory
// pointed to by the char * is allocated using malloc() and should be
// freed by the caller using free().
// Return the embedded engine version.
//
// Return value: a version string, in the following format:
// "<major>.<minor>.<patch>", e.g. "1.2.3".
// This interface is compatible following semver conventions:
// <major> changes for incompatible api changes, <minor> for
// backwards-compatible additions, <patch> for compatible bug
// fixes.
char* falco_engine_embed_get_version();
// Initialize a falco engine.
//
// Arguments:
// - rc: pointer to an integer containing a falco_engine_embed_rc value.
//
// Return value: pointer to the engine state that is passed to
// other API functions.
falco_engine_embed_t* falco_engine_embed_init(int32_t *rc);
// Destroy a falco engine. This frees any resources allocated in
// init(). If open() has been called, close() should be called before
// destroy().
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_destroy(falco_engine_embed_t *engine, char *errstr);
// Load either a falco source or extractor plugin.
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - path: a file path pointing to a dynamic library that
// can be dlopen()ed.
// - init_config: a string that will be passed to the plugin's
// init() function.
// - open_params: a string that will be passed to the
// plugin's open() function.
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_load_plugin(falco_engine_embed_t *engine,
const char *path,
const char* init_config,
const char* open_params,
char **errstr);
// Load the provided rules content. These rules are applied on
// top of any previously loaded rules content
// (e.g. appending/overriding rule/macro/list objects as
// specified via "append:" properties)
//
// NOTE: Plugins should be loaded before any rules are loaded.
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - rules_content: a null-terminated string containing
// yaml rules content.
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_load_rules_content(falco_engine_embed_t *engine,
const char *rules_content,
char **errstr);
// Enable/disable an event source.
// By default all event sources are enabled. This function
// enables/disables specific event sources.
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - source: an int containing a falco_engine_embed_evt_source value.
// - enabled: whether to enable or disable the provided source
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_enable_source(falco_engine_embed_t *engine,
int32_t source,
bool enabled,
char **errstr);
// Open the engine, which starts event processing and matching
// against the loaded set of rules.
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_open(falco_engine_embed_t *engine,
char **errstr);
// Close the engine, which stops event processing.
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_close(falco_engine_embed_t *engine,
char **errstr);
// Receive the next result (e.g. an event that matched a
// rule). This function blocks until the next result is
// available. close() is called, or an error occurs.
//
// Arguments:
// - engine: returned by a prior succesful call to init().
// - result: a pointer to a falco_engine_embed_result struct
// pointer. On success, a struct will be allocated, and filled in
// with allocated char* values, and the pointer updated to point to
// the allocated struct.
// - errstr: on error, errstr will point to an allocated
// string with additional details on the errror. The string
// must be freed via a call to free().
//
// Return value: an integer containing a falco_engine_embed_rc
// value.
int32_t falco_engine_embed_next_result(falco_engine_embed_t *engine,
falco_engine_embed_result **result,
char **errstr);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -16,9 +16,9 @@ limitations under the License.
#include <fstream>
#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",
@@ -48,33 +48,73 @@ falco_common::~falco_common()
}
}
void falco_common::init()
void falco_common::set_inspector(sinsp *inspector)
{
// 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)
m_inspector = inspector;
}
void falco_common::init(const char *lua_main_filename, const char *alternate_lua_dir)
{
ifstream is;
string lua_dir = FALCO_ENGINE_LUA_DIR;
string lua_main_path = lua_dir + lua_main_filename;
is.open(lua_main_path);
if (!is.is_open())
{
lua_getglobal(m_ls, "package");
lua_getfield(m_ls, -1, "preload");
lua_dir = alternate_lua_dir;
lua_main_path = lua_dir + lua_main_filename;
if(luaL_loadstring(m_ls, pair.first))
is.open(lua_main_path);
if (!is.is_open())
{
throw falco_exception("Failed to load embedded lua code " +
string(pair.second) + ": " + lua_tostring(m_ls, -1));
throw falco_exception("Could not find Falco Lua entrypoint (tried " +
string(FALCO_ENGINE_LUA_DIR) + lua_main_filename + ", " +
string(alternate_lua_dir) + lua_main_filename + ")");
}
lua_setfield(m_ls, -2, pair.second);
}
// 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)
// Initialize Lua interpreter
add_lua_path(lua_dir);
// Load the main program, which defines all the available functions.
string scriptstr((istreambuf_iterator<char>(is)),
istreambuf_iterator<char>());
if(luaL_loadstring(m_ls, scriptstr.c_str()) || lua_pcall(m_ls, 0, 0, 0))
{
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));
}
throw falco_exception("Failed to load script " +
lua_main_path + ": " + lua_tostring(m_ls, -1));
}
}
void falco_common::add_lua_path(string &path)
{
string cpath = string(path);
path += "?.lua";
cpath += "?.so";
lua_getglobal(m_ls, "package");
lua_getfield(m_ls, -1, "path");
string cur_path = lua_tostring(m_ls, -1 );
cur_path += ';';
lua_pop(m_ls, 1);
cur_path.append(path.c_str());
lua_pushstring(m_ls, cur_path.c_str());
lua_setfield(m_ls, -2, "path");
lua_getfield(m_ls, -1, "cpath");
string cur_cpath = lua_tostring(m_ls, -1 );
cur_cpath += ';';
lua_pop(m_ls, 1);
cur_cpath.append(cpath.c_str());
lua_pushstring(m_ls, cur_cpath.c_str());
lua_setfield(m_ls, -2, "cpath");
lua_pop(m_ls, 1);
}

View File

@@ -69,7 +69,9 @@ public:
falco_common();
virtual ~falco_common();
void init();
void init(const char *lua_main_filename, const char *alternate_lua_dir);
void set_inspector(sinsp *inspector);
// Priority levels, as a vector of strings
static std::vector<std::string> priority_names;
@@ -91,4 +93,9 @@ protected:
lua_State *m_ls;
std::mutex m_ls_semaphore;
};
sinsp *m_inspector;
private:
void add_lua_path(std::string &path);
};

View File

@@ -19,12 +19,10 @@ limitations under the License.
#include <string>
#include <fstream>
#include <sinsp.h>
#include <plugin.h>
#include "falco_engine.h"
#include "falco_utils.h"
#include "falco_engine_version.h"
#include "config_falco_engine.h"
#include "formats.h"
@@ -39,12 +37,13 @@ extern "C" {
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)
: m_next_ruleset_id(0),
nlohmann::json::json_pointer falco_engine::k8s_audit_time = "/stageTimestamp"_json_pointer;
falco_engine::falco_engine(bool seed_rng, const std::string& alternate_lua_dir)
: m_rules(NULL), 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)
@@ -52,21 +51,29 @@ falco_engine::falco_engine(bool seed_rng)
luaopen_lpeg(m_ls);
luaopen_yaml(m_ls);
falco_common::init();
falco_common::init(m_lua_main_filename.c_str(), alternate_lua_dir.c_str());
falco_rules::init(m_ls);
m_required_plugin_versions.clear();
m_sinsp_rules.reset(new falco_sinsp_ruleset());
m_k8s_audit_rules.reset(new falco_ruleset());
if(seed_rng)
{
srandom((unsigned) getpid());
}
m_default_ruleset_id = find_ruleset_id(s_default_ruleset);
m_default_ruleset_id = find_ruleset_id(m_default_ruleset);
// Create this now so we can potentially list filters and exit
m_json_factory = make_shared<json_event_filter_factory>();
}
falco_engine::~falco_engine()
{
if (m_rules)
{
delete m_rules;
}
}
uint32_t falco_engine::engine_version()
@@ -74,76 +81,65 @@ uint32_t falco_engine::engine_version()
return (uint32_t) FALCO_ENGINE_VERSION;
}
// Return a key that uniquely represents a field class.
// For now, we assume name + shortdesc is unique.
static std::string fieldclass_key(const gen_event_filter_factory::filter_fieldclass_info &fld_info)
#define DESCRIPTION_TEXT_START 16
#define CONSOLE_LINE_LEN 79
void falco_engine::list_fields(bool names_only)
{
return fld_info.name + fld_info.shortdesc;
}
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.
std::map<std::string,std::set<std::string>> fieldclass_event_sources;
// Do a first pass to group together classes that are
// applicable to multiple event sources.
for(auto &it : m_filter_factories)
for(auto &chk_field : json_factory().get_fields())
{
if(source != "" && source != it.first)
if(!names_only)
{
continue;
}
for(auto &fld_class : it.second->get_fields())
{
fieldclass_event_sources[fieldclass_key(fld_class)].insert(it.first);
}
}
// The set of field classes already printed. Used to avoid
// printing field classes multiple times for different sources
std::set<std::string> seen_fieldclasses;
// In the second pass, actually print info, skipping duplicate
// field classes and also printing info on supported sources.
for(auto &it : m_filter_factories)
{
if(source != "" && source != it.first)
{
continue;
}
for(auto &fld_class : it.second->get_fields())
{
std::string key = fieldclass_key(fld_class);
if(seen_fieldclasses.find(key) != seen_fieldclasses.end())
printf("\n----------------------\n");
printf("Field Class: %s (%s)\n\n", chk_field.m_name.c_str(), chk_field.m_desc.c_str());
if(chk_field.m_class_info != "")
{
std::string str = falco::utils::wrap_text(chk_field.m_class_info, 0, 0, CONSOLE_LINE_LEN);
printf("%s\n", str.c_str());
}
}
for(auto &field : chk_field.m_fields)
{
printf("%s", field.m_name.c_str());
if(names_only)
{
printf("\n");
continue;
}
uint32_t namelen = field.m_name.size();
seen_fieldclasses.insert(key);
if(!names_only)
if(namelen >= DESCRIPTION_TEXT_START)
{
printf("%s\n", fld_class.as_string(verbose,
fieldclass_event_sources[fieldclass_key(fld_class)]).c_str());
printf("\n");
namelen = 0;
}
else
{
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());
}
for(uint32_t l = 0; l < DESCRIPTION_TEXT_START - namelen; l++)
{
printf(" ");
}
std::string desc = field.m_desc;
switch(field.m_idx_mode)
{
case json_event_filter_check::IDX_REQUIRED:
case json_event_filter_check::IDX_ALLOWED:
desc += " (";
desc += json_event_filter_check::s_index_mode_strs[field.m_idx_mode];
desc += ", ";
desc += json_event_filter_check::s_index_type_strs[field.m_idx_type];
desc += ")";
break;
case json_event_filter_check::IDX_NONE:
default:
break;
};
std::string str = falco::utils::wrap_text(desc, namelen, DESCRIPTION_TEXT_START, CONSOLE_LINE_LEN);
printf("%s\n", str.c_str());
}
}
}
@@ -157,18 +153,33 @@ 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)
{
if(!m_rules)
// The engine must have been given an inspector by now.
if(! m_inspector)
{
m_rules.reset(new falco_rules(this,
m_ls));
for(auto const &it : m_filter_factories)
{
m_rules->add_filter_factory(it.first, it.second);
}
throw falco_exception("No inspector provided");
}
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);
if(!m_sinsp_factory)
{
m_sinsp_factory = make_shared<sinsp_filter_factory>(m_inspector);
}
if(!m_rules)
{
m_rules = new falco_rules(m_inspector,
this,
m_ls);
}
// Note that falco_formats is added to the lua state used
// by the falco engine only. Within the engine, only
// formats.formatter is used, so we can unconditionally set
// json_output to false.
bool json_output = false;
bool json_include_output_property = false;
falco_formats::init(m_inspector, this, m_ls, json_output, json_include_output_property);
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority, required_engine_version);
}
void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events)
@@ -201,10 +212,13 @@ void falco_engine::enable_rule(const string &substring, bool enabled, const stri
uint16_t ruleset_id = find_ruleset_id(ruleset);
bool match_exact = false;
for(auto &it : m_rulesets)
{
it.second->enable(substring, match_exact, enabled, ruleset_id);
}
m_sinsp_rules->enable(substring, match_exact, enabled, ruleset_id);
m_k8s_audit_rules->enable(substring, match_exact, enabled, ruleset_id);
}
void falco_engine::enable_rule(const string &substring, bool enabled)
{
enable_rule(substring, enabled, m_default_ruleset);
}
void falco_engine::enable_rule_exact(const string &rule_name, bool enabled, const string &ruleset)
@@ -212,20 +226,26 @@ void falco_engine::enable_rule_exact(const string &rule_name, bool enabled, cons
uint16_t ruleset_id = find_ruleset_id(ruleset);
bool match_exact = true;
for(auto &it : m_rulesets)
{
it.second->enable(rule_name, match_exact, enabled, ruleset_id);
}
m_sinsp_rules->enable(rule_name, match_exact, enabled, ruleset_id);
m_k8s_audit_rules->enable(rule_name, match_exact, enabled, ruleset_id);
}
void falco_engine::enable_rule_exact(const string &rule_name, bool enabled)
{
enable_rule_exact(rule_name, enabled, m_default_ruleset);
}
void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled, const string &ruleset)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
for(auto &it : m_rulesets)
{
it.second->enable_tags(tags, enabled, ruleset_id);
}
m_sinsp_rules->enable_tags(tags, enabled, ruleset_id);
m_k8s_audit_rules->enable_tags(tags, enabled, ruleset_id);
}
void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled)
{
enable_rule_by_tag(tags, enabled, m_default_ruleset);
}
void falco_engine::set_min_priority(falco_common::priority_type priority)
@@ -251,85 +271,68 @@ uint64_t falco_engine::num_rules_for_ruleset(const std::string &ruleset)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
uint64_t ret = 0;
for(auto &it : m_rulesets)
{
ret += it.second->num_rules_for_ruleset(ruleset_id);
}
return ret;
return m_sinsp_rules->num_rules_for_ruleset(ruleset_id) +
m_k8s_audit_rules->num_rules_for_ruleset(ruleset_id);
}
void falco_engine::evttypes_for_ruleset(std::string &source, std::set<uint16_t> &evttypes, const std::string &ruleset)
void falco_engine::evttypes_for_ruleset(std::vector<bool> &evttypes, const std::string &ruleset)
{
uint16_t ruleset_id = find_ruleset_id(ruleset);
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
it->second->evttypes_for_ruleset(evttypes, ruleset_id);
return m_sinsp_rules->evttypes_for_ruleset(evttypes, ruleset_id);
}
std::shared_ptr<gen_event_formatter> falco_engine::create_formatter(const std::string &source,
const std::string &output)
void falco_engine::syscalls_for_ruleset(std::vector<bool> &syscalls, const std::string &ruleset)
{
auto it = m_format_factories.find(source);
uint16_t ruleset_id = find_ruleset_id(ruleset);
if(it == m_format_factories.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
return it->second->create_formatter(output);
return m_sinsp_rules->syscalls_for_ruleset(syscalls, ruleset_id);
}
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::string &source, gen_event *ev, uint16_t ruleset_id)
unique_ptr<falco_engine::rule_result> falco_engine::process_sinsp_event(sinsp_evt *ev, uint16_t ruleset_id)
{
if(should_drop_evt())
{
return unique_ptr<struct rule_result>();
}
auto it = m_rulesets.find(source);
if(it == m_rulesets.end())
{
string err = "Unknown event source " + source;
throw falco_exception(err);
}
if (!it->second->run(ev, ruleset_id))
if(!m_sinsp_rules->run(ev, ruleset_id))
{
return unique_ptr<struct rule_result>();
}
unique_ptr<struct rule_result> res(new rule_result());
res->source = source;
res->source = "syscall";
populate_rule_result(res, ev);
return res;
}
unique_ptr<falco_engine::rule_result> falco_engine::process_event(std::string &source, gen_event *ev)
unique_ptr<falco_engine::rule_result> falco_engine::process_sinsp_event(sinsp_evt *ev)
{
return process_event(source, ev, m_default_ruleset_id);
return process_sinsp_event(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)
unique_ptr<falco_engine::rule_result> falco_engine::process_k8s_audit_event(json_event *ev, uint16_t ruleset_id)
{
m_filter_factories[source] = filter_factory;
m_format_factories[source] = formatter_factory;
if(should_drop_evt())
{
return unique_ptr<struct rule_result>();
}
std::shared_ptr<falco_ruleset> ruleset(new falco_ruleset());
m_rulesets[source] = ruleset;
// All k8s audit events have the single tag "1".
if(!m_k8s_audit_rules->run((gen_event *) ev, 1, ruleset_id))
{
return unique_ptr<struct rule_result>();
}
unique_ptr<struct rule_result> res(new rule_result());
res->source = "k8s_audit";
populate_rule_result(res, ev);
return res;
}
void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen_event *ev)
@@ -339,29 +342,18 @@ void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen
if(lua_isfunction(m_ls, -1))
{
lua_pushnumber(m_ls, ev->get_check_id());
if(lua_pcall(m_ls, 1, 5, 0) != 0)
if(lua_pcall(m_ls, 1, 4, 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);
const char *p = lua_tostring(m_ls, -4);
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
res->priority_num = (falco_common::priority_type) lua_tonumber(m_ls, -3);
res->format = lua_tostring(m_ls, -2);
// Exception fields are passed back as a table
lua_pushnil(m_ls); /* first key */
@@ -382,6 +374,84 @@ void falco_engine::populate_rule_result(unique_ptr<struct rule_result> &res, gen
}
}
bool falco_engine::parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top)
{
// Note that nlohmann::basic_json::value can throw nlohmann::basic_json::type_error (302, 306)
try
{
// If the object is an array, call parse_k8s_audit_json again for each item.
if(j.is_array())
{
if(top)
{
for(auto &item : j)
{
// Note we only handle a single top level array, to
// avoid excessive recursion.
if(! parse_k8s_audit_json(item, evts, false))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
// If the kind is EventList, split it into individual events
if(j.value("kind", "<NA>") == "EventList")
{
for(auto &je : j["items"])
{
evts.emplace_back();
je["kind"] = "Event";
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(je.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
std::string tmp;
sinsp_utils::ts_to_string(ns, &tmp, false, true);
evts.back().set_jevt(je, ns);
}
return true;
}
else if(j.value("kind", "<NA>") == "Event")
{
evts.emplace_back();
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(j.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
evts.back().set_jevt(j, ns);
return true;
}
else
{
return false;
}
}
catch(exception &e)
{
return false;
}
}
unique_ptr<falco_engine::rule_result> falco_engine::process_k8s_audit_event(json_event *ev)
{
return process_k8s_audit_event(ev, m_default_ruleset_id);
}
void falco_engine::describe_rule(string *rule)
{
return m_rules->describe_rule(rule);
@@ -408,68 +478,29 @@ void falco_engine::print_stats()
}
void falco_engine::add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<std::string> &tags)
void falco_engine::add_sinsp_filter(string &rule,
set<uint32_t> &evttypes,
set<uint32_t> &syscalls,
set<string> &tags,
sinsp_filter* filter)
{
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);
m_sinsp_rules->add(rule, evttypes, syscalls, tags, filter);
}
bool falco_engine::is_source_valid(const std::string &source)
void falco_engine::add_k8s_audit_filter(string &rule,
set<string> &tags,
json_event_filter* filter)
{
return (m_rulesets.find(source) != m_rulesets.end());
}
// All k8s audit events have a single tag "1".
std::set<uint32_t> event_tags = {1};
bool falco_engine::is_plugin_compatible(const std::string &name,
const std::string &version,
std::string &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;
m_k8s_audit_rules->add(rule, tags, event_tags, filter);
}
void falco_engine::clear_filters()
{
m_rulesets.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();
m_sinsp_rules.reset(new falco_sinsp_ruleset());
m_k8s_audit_rules.reset(new falco_ruleset());
}
void falco_engine::set_sampling_ratio(uint32_t sampling_ratio)
@@ -503,3 +534,23 @@ inline bool falco_engine::should_drop_evt()
double coin = (random() * (1.0/RAND_MAX));
return (coin >= (1.0/(m_sampling_multiplier * m_sampling_ratio)));
}
sinsp_filter_factory &falco_engine::sinsp_factory()
{
if(!m_sinsp_factory)
{
throw falco_exception("No sinsp factory created yet");
}
return *(m_sinsp_factory.get());
}
json_event_filter_factory &falco_engine::json_factory()
{
if(!m_json_factory)
{
throw falco_exception("No json factory created yet");
}
return *(m_json_factory.get());
}

View File

@@ -28,10 +28,14 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "gen_filter.h"
#include "sinsp.h"
#include "filter.h"
#include "json_evt.h"
#include "rules.h"
#include "ruleset.h"
#include "config_falco_engine.h"
#include "falco_common.h"
//
@@ -43,7 +47,7 @@ limitations under the License.
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
@@ -53,8 +57,7 @@ public:
static uint32_t engine_version();
// 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);
void list_fields(bool names_only=false);
//
// Load rules either directly or from a filename.
@@ -77,16 +80,25 @@ public:
// be passed as an argument to process_event(). This allows
// for different sets of rules being active at once.
//
void enable_rule(const std::string &substring, bool enabled, const std::string &ruleset = s_default_ruleset);
void enable_rule(const std::string &substring, bool enabled, const std::string &ruleset);
// Wrapper that assumes the default ruleset
void enable_rule(const std::string &substring, bool enabled);
// Like enable_rule, but the rule name must be an exact match.
void enable_rule_exact(const std::string &rule_name, bool enabled, const std::string &ruleset = s_default_ruleset);
void enable_rule_exact(const std::string &rule_name, bool enabled, const std::string &ruleset);
// Wrapper that assumes the default ruleset
void enable_rule_exact(const std::string &rule_name, bool enabled);
//
// Enable/Disable any rules with any of the provided tags (set, exact matches only)
//
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled, const std::string &ruleset = s_default_ruleset);
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled, const std::string &ruleset);
// Wrapper that assumes the default ruleset
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled);
// Only load rules having this priority or more severe.
void set_min_priority(falco_common::priority_type priority);
@@ -140,8 +152,8 @@ public:
//
void set_extra(string &extra, bool replace_container_info);
// Represents the result of matching an event against a set of
// rules.
// **Methods Related to k8s audit log events, which are
// **represented as json objects.
struct rule_result {
gen_event *evt;
std::string rule;
@@ -149,9 +161,18 @@ public:
falco_common::priority_type priority_num;
std::string format;
std::set<std::string> exception_fields;
std::set<std::string> tags;
};
//
// Given a raw json object, return a list of k8s audit event
// objects that represent the object. This method handles
// things such as EventList splitting.
//
// Returns true if the json object was recognized as a k8s
// audit event(s), false otherwise.
//
bool parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top=true);
//
// Given an event, check it against the set of rules in the
// engine and if a matching rule is found, return details on
@@ -163,81 +184,84 @@ public:
// with a ruleset string.
//
// the returned rule_result is allocated and must be delete()d.
std::unique_ptr<rule_result> process_event(std::string &source, gen_event *ev, uint16_t ruleset_id);
std::unique_ptr<rule_result> process_k8s_audit_event(json_event *ev, uint16_t ruleset_id);
//
// Wrapper assuming the default ruleset
//
std::unique_ptr<rule_result> process_event(std::string &source, gen_event *ev);
std::unique_ptr<rule_result> process_k8s_audit_event(json_event *ev);
//
// Configure the engine to support events with the provided
// source, with the provided filter factory and formatter factory.
// Add a k8s_audit filter to the engine
//
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);
void add_k8s_audit_filter(std::string &rule,
std::set<std::string> &tags,
json_event_filter* filter);
// Return whether or not there is a valid filter/formatter
// factory for this source.
bool is_source_valid(const std::string &source);
// **Methods Related to Sinsp Events e.g system calls
//
// Given a ruleset, fill in a bitset containing the event
// types for which this ruleset can run.
//
void evttypes_for_ruleset(std::vector<bool> &evttypes, const std::string &ruleset);
//
// Add a filter for the provided event source to the engine
// Given a ruleset, fill in a bitset containing the syscalls
// for which this ruleset can run.
//
void add_filter(std::shared_ptr<gen_event_filter> filter,
std::string &rule,
std::string &source,
std::set<std::string> &tags);
void syscalls_for_ruleset(std::vector<bool> &syscalls, const std::string &ruleset);
//
// Given an event source and ruleset, fill in a bitset
// containing the event types for which this ruleset can run.
// Given an event, check it against the set of rules in the
// engine and if a matching rule is found, return details on
// the rule that matched. If no rule matched, returns NULL.
//
void evttypes_for_ruleset(std::string &source,
std::set<uint16_t> &evttypes,
const std::string &ruleset = s_default_ruleset);
// When ruleset_id is provided, use the enabled/disabled status
// associated with the provided ruleset. This is only useful
// when you have previously called enable_rule/enable_rule_by_tag
// with a ruleset string.
//
// the returned rule_result is allocated and must be delete()d.
std::unique_ptr<rule_result> process_sinsp_event(sinsp_evt *ev, uint16_t ruleset_id);
//
// Given a source and output string, return an
// gen_event_formatter that can format output strings for an
// event.
// Wrapper assuming the default ruleset
//
std::shared_ptr<gen_event_formatter> create_formatter(const std::string &source,
const std::string &output);
std::unique_ptr<rule_result> process_sinsp_event(sinsp_evt *ev);
// Return whether the provided plugin name + version is
// compatible with the current set of loaded rules files.
// required_version will be filled in with the required
// version when the method returns false.
bool is_plugin_compatible(const std::string &name, const std::string &version, std::string &required_version);
//
// Add a filter, which is related to the specified set of
// event types/syscalls, to the engine.
//
void add_sinsp_filter(std::string &rule,
std::set<uint32_t> &evttypes,
std::set<uint32_t> &syscalls,
std::set<std::string> &tags,
sinsp_filter* filter);
sinsp_filter_factory &sinsp_factory();
json_event_filter_factory &json_factory();
private:
static nlohmann::json::json_pointer k8s_audit_time;
//
// Determine whether the given event should be matched at all
// against the set of rules, given the current sampling
// ratio/multiplier.
//
inline bool should_drop_evt();
shared_ptr<sinsp_filter_factory> m_sinsp_factory;
shared_ptr<json_event_filter_factory> m_json_factory;
// 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;
// Maps from event source to object that can format output strings in rules
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::map<std::string, std::shared_ptr<falco_ruleset>> m_rulesets;
std::unique_ptr<falco_rules> m_rules;
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;
std::unique_ptr<falco_sinsp_ruleset> m_sinsp_rules;
std::unique_ptr<falco_ruleset> m_k8s_audit_rules;
void populate_rule_result(unique_ptr<struct rule_result> &res, gen_event *ev);
@@ -264,7 +288,8 @@ private:
uint32_t m_sampling_ratio;
double m_sampling_multiplier;
static const std::string s_default_ruleset;
std::string m_lua_main_filename = "rule_loader.lua";
std::string m_default_ruleset = "falco-default-ruleset";
uint32_t m_default_ruleset_id;
std::string m_extra;

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2021 The Falco Authors.
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.
@@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// The version of rules/filter fields/etc supported by this Falco
// The version of rules/filter fields/etc supported by this falco
// engine.
#define FALCO_ENGINE_VERSION (11)
#define FALCO_ENGINE_VERSION (9)
// This is the result of running "falco --list -N | sha256sum" and
// represents the fields supported by this version of Falco. It's used
// 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 "4de812495f8529ac20bda2b9774462b15911a51df293d59fe9ccb6b922fdeb9d"
#define FALCO_FIELDS_CHECKSUM "8183621f52451d842036eee409e2ed920d9c91bab33e0c4a44e4a871378d103f"

View File

@@ -27,6 +27,31 @@ namespace falco
namespace utils
{
std::string wrap_text(const std::string& str, uint32_t initial_pos, uint32_t indent, uint32_t line_len)
{
std::string ret;
size_t len = str.size();
for(uint32_t l = 0; l < len; l++)
{
if(l % (line_len - indent) == 0 && l != 0)
{
ret += "\n";
for(uint32_t m = 0; m < indent; m++)
{
ret += " ";
}
}
ret += str.at(l);
}
ret += "\n";
return ret;
}
uint32_t hardware_concurrency()
{
auto hc = std::thread::hardware_concurrency();

View File

@@ -20,54 +20,169 @@ limitations under the License.
#include "falco_engine.h"
#include "banned.h" // This raises a compilation error when certain functions are used
falco_formats::falco_formats(falco_engine *engine,
bool json_include_output_property,
bool json_include_tags_property)
: m_falco_engine(engine),
m_json_include_output_property(json_include_output_property),
m_json_include_tags_property(json_include_tags_property)
{
}
sinsp *falco_formats::s_inspector = NULL;
falco_engine *falco_formats::s_engine = NULL;
bool falco_formats::s_json_output = false;
bool falco_formats::s_json_include_output_property = true;
std::unique_ptr<sinsp_evt_formatter_cache> falco_formats::s_formatters = NULL;
falco_formats::~falco_formats()
{
}
string falco_formats::format_event(gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format, std::set<std::string> &tags)
{
string line;
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_falco_engine->create_formatter(source, format);
// Format the original output string, regardless of output format
formatter->tostring_withformat(evt, line, gen_event_formatter::OF_NORMAL);
if(formatter->get_output_format() == gen_event_formatter::OF_JSON)
const static struct luaL_Reg ll_falco[] =
{
string json_line;
{"formatter", &falco_formats::lua_formatter},
{"free_formatter", &falco_formats::lua_free_formatter},
{NULL, NULL}};
// Format the event into a json object with all fields resolved
formatter->tostring(evt, json_line);
void falco_formats::init(sinsp *inspector,
falco_engine *engine,
lua_State *ls,
bool json_output,
bool json_include_output_property)
{
s_inspector = inspector;
s_engine = engine;
s_json_output = json_output;
s_json_include_output_property = json_include_output_property;
// The formatted string might have a leading newline. If it does, remove it.
if(json_line[0] == '\n')
// todo(leogr): we should have used std::make_unique, but we cannot since it's not C++14
s_formatters = std::unique_ptr<sinsp_evt_formatter_cache>(new sinsp_evt_formatter_cache(s_inspector));
luaL_openlib(ls, "formats", ll_falco, 0);
}
int falco_formats::lua_formatter(lua_State *ls)
{
string source = luaL_checkstring(ls, -2);
string format = luaL_checkstring(ls, -1);
try
{
if(source == "syscall")
{
json_line.erase(0, 1);
sinsp_evt_formatter *formatter;
formatter = new sinsp_evt_formatter(s_inspector, format);
lua_pushnil(ls);
lua_pushlightuserdata(ls, formatter);
}
else
{
json_event_formatter *formatter;
formatter = new json_event_formatter(s_engine->json_factory(), format);
lua_pushnil(ls);
lua_pushlightuserdata(ls, formatter);
}
}
catch(exception &e)
{
std::ostringstream os;
// For JSON output, the formatter returned a json-as-text
// object containing all the fields in the original format
// message as well as the event time in ns. Use this to build
// a more detailed object containing the event time, rule,
// severity, full output, and fields.
os << "Invalid output format '"
<< format
<< "': '"
<< e.what()
<< "'";
lua_pushstring(ls, os.str().c_str());
lua_pushnil(ls);
}
return 2;
}
int falco_formats::lua_free_formatter(lua_State *ls)
{
if(!lua_islightuserdata(ls, -1) ||
!lua_isstring(ls, -2))
{
luaL_error(ls, "Invalid argument passed to free_formatter");
}
string source = luaL_checkstring(ls, -2);
if(source == "syscall")
{
sinsp_evt_formatter *formatter = (sinsp_evt_formatter *)lua_topointer(ls, -1);
delete(formatter);
}
else
{
json_event_formatter *formatter = (json_event_formatter *)lua_topointer(ls, -1);
delete(formatter);
}
return 0;
}
string falco_formats::format_event(const gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format)
{
string line;
string json_line;
string sformat = format;
if(strcmp(source.c_str(), "syscall") == 0)
{
// This is "output"
s_formatters->tostring((sinsp_evt *)evt, sformat, &line);
if(s_json_output)
{
sinsp_evt::param_fmt cur_fmt = s_inspector->get_buffer_format();
switch(cur_fmt)
{
case sinsp_evt::PF_NORMAL:
s_inspector->set_buffer_format(sinsp_evt::PF_JSON);
break;
case sinsp_evt::PF_EOLS:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONEOLS);
break;
case sinsp_evt::PF_HEX:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONHEX);
break;
case sinsp_evt::PF_HEXASCII:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONHEXASCII);
break;
case sinsp_evt::PF_BASE64:
s_inspector->set_buffer_format(sinsp_evt::PF_JSONBASE64);
break;
default:
// do nothing
break;
}
// This is output fields
s_formatters->tostring((sinsp_evt *)evt, sformat, &json_line);
// The formatted string might have a leading newline. If it does, remove it.
if(json_line[0] == '\n')
{
json_line.erase(0, 1);
}
s_inspector->set_buffer_format(cur_fmt);
}
}
else
{
json_event_formatter formatter(s_engine->json_factory(), sformat);
line = formatter.tostring((json_event *)evt);
if(s_json_output)
{
json_line = formatter.tojson((json_event *)evt);
}
}
// For JSON output, the formatter returned a json-as-text
// object containing all the fields in the original format
// message as well as the event time in ns. Use this to build
// a more detailed object containing the event time, rule,
// severity, full output, and fields.
if(s_json_output)
{
Json::Value event;
Json::Value rule_tags;
Json::FastWriter writer;
string full_line;
unsigned int rule_tags_idx = 0;
// Convert the time-as-nanoseconds to a more json-friendly ISO8601.
time_t evttime = evt->get_ts() / 1000000000;
@@ -82,31 +197,13 @@ string falco_formats::format_event(gen_event *evt, const std::string &rule, cons
event["time"] = iso8601evttime;
event["rule"] = rule;
event["priority"] = level;
event["source"] = source;
if(m_json_include_output_property)
if(s_json_include_output_property)
{
// This is the filled-in output line.
event["output"] = line;
}
if(m_json_include_tags_property)
{
if (tags.size() == 0)
{
// This sets an empty array
rule_tags = Json::arrayValue;
}
else
{
for (auto &tag : tags)
{
rule_tags[rule_tags_idx++] = tag;
}
}
event["tags"] = rule_tags;
}
full_line = writer.write(event);
// Json::FastWriter may add a trailing newline. If it
@@ -129,19 +226,19 @@ string falco_formats::format_event(gen_event *evt, const std::string &rule, cons
return line.c_str();
}
map<string, string> falco_formats::get_field_values(gen_event *evt, const std::string &source,
const std::string &format)
map<string, string> falco_formats::resolve_tokens(const gen_event *evt, const std::string &source, const std::string &format)
{
std::shared_ptr<gen_event_formatter> formatter;
formatter = m_falco_engine->create_formatter(source, format);
map<string, string> ret;
if (! formatter->get_field_values(evt, ret))
string sformat = format;
map<string, string> values;
if(source == "syscall")
{
throw falco_exception("Could not extract all field values from event");
s_formatters->resolve_tokens((sinsp_evt *)evt, sformat, values);
}
return ret;
// k8s_audit
else
{
json_event_formatter json_formatter(s_engine->json_factory(), sformat);
values = json_formatter.tomap((json_event *)evt);
}
return values;
}

View File

@@ -16,8 +16,7 @@ limitations under the License.
#pragma once
#include <string>
#include <map>
#include "sinsp.h"
extern "C"
{
@@ -26,26 +25,35 @@ extern "C"
#include "lauxlib.h"
}
#include <gen_filter.h>
#include "json_evt.h"
#include "falco_engine.h"
class sinsp_evt_formatter;
class falco_formats
{
public:
falco_formats(falco_engine *engine,
bool json_include_output_property,
bool json_include_tags_property);
virtual ~falco_formats();
static void init(sinsp *inspector,
falco_engine *engine,
lua_State *ls,
bool json_output,
bool json_include_output_property);
std::string format_event(gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format, std::set<std::string> &tags);
// formatter = falco.formatter(format_string)
static int lua_formatter(lua_State *ls);
map<string, string> get_field_values(gen_event *evt, const std::string &source,
const std::string &format);
// falco.free_formatter(formatter)
static int lua_free_formatter(lua_State *ls);
protected:
falco_engine *m_falco_engine;
bool m_json_include_output_property;
bool m_json_include_tags_property;
static string format_event(const gen_event *evt, const std::string &rule, const std::string &source,
const std::string &level, const std::string &format);
static map<string, string> resolve_tokens(const gen_event *evt, const std::string &source,
const std::string &format);
static sinsp *s_inspector;
static falco_engine *s_engine;
static std::unique_ptr<sinsp_evt_formatter_cache> s_formatters;
static bool s_json_output;
static bool s_json_include_output_property;
};

View File

@@ -50,81 +50,6 @@ uint64_t json_event::get_ts() const
return m_event_ts;
}
static nlohmann::json::json_pointer k8s_audit_time = "/stageTimestamp"_json_pointer;
bool falco_k8s_audit::parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top)
{
// Note that nlohmann::basic_json::value can throw nlohmann::basic_json::type_error (302, 306)
try
{
// If the object is an array, call parse_k8s_audit_json again for each item.
if(j.is_array())
{
if(top)
{
for(auto &item : j)
{
// Note we only handle a single top level array, to
// avoid excessive recursion.
if(! parse_k8s_audit_json(item, evts, false))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
// If the kind is EventList, split it into individual events
if(j.value("kind", "<NA>") == "EventList")
{
for(auto &je : j["items"])
{
evts.emplace_back();
je["kind"] = "Event";
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(je.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
std::string tmp;
sinsp_utils::ts_to_string(ns, &tmp, false, true);
evts.back().set_jevt(je, ns);
}
return true;
}
else if(j.value("kind", "<NA>") == "Event")
{
evts.emplace_back();
uint64_t ns = 0;
if(!sinsp_utils::parse_iso_8601_utc_string(j.value(k8s_audit_time, "<NA>"), ns))
{
return false;
}
evts.back().set_jevt(j, ns);
return true;
}
else
{
return false;
}
}
catch(exception &e)
{
return false;
}
}
json_event_value::json_event_value()
{
}
@@ -690,7 +615,7 @@ size_t json_event_filter_check::parsed_size()
}
}
json_event_filter_check::check_info &json_event_filter_check::get_info()
json_event_filter_check::check_info &json_event_filter_check::get_fields()
{
return m_info;
}
@@ -1432,7 +1357,7 @@ json_event_filter_factory::json_event_filter_factory()
for(auto &chk : m_defined_checks)
{
m_info.push_back(chk->get_info());
m_info.push_back(chk->get_fields());
}
}
@@ -1464,96 +1389,36 @@ gen_event_filter_check *json_event_filter_factory::new_filtercheck(const char *f
return NULL;
}
std::list<gen_event_filter_factory::filter_fieldclass_info> json_event_filter_factory::get_fields()
std::list<json_event_filter_check::check_info> &json_event_filter_factory::get_fields()
{
std::list<gen_event_filter_factory::filter_fieldclass_info> ret;
// It's not quite copy to the public information, as m_info
// has addl info about indexing. That info is added to the
// description.
for(auto &chk: m_defined_checks)
{
json_event_filter_check::check_info &info = chk->get_info();
gen_event_filter_factory::filter_fieldclass_info cinfo;
cinfo.name = info.m_name;
cinfo.desc = info.m_desc;
cinfo.shortdesc = info.m_shortdesc;
for(auto &field : info.m_fields)
{
gen_event_filter_factory::filter_field_info info;
info.name = field.m_name;
info.desc = field.m_desc;
// All json fields work on strings
info.data_type = "CHARBUF";
switch(field.m_idx_mode)
{
case json_event_filter_check::IDX_REQUIRED:
case json_event_filter_check::IDX_ALLOWED:
info.tags.insert(json_event_filter_check::s_index_mode_strs[field.m_idx_mode]);
break;
case json_event_filter_check::IDX_NONE:
default:
break;
};
cinfo.fields.emplace_back(std::move(info));
}
ret.emplace_back(std::move(cinfo));
}
return ret;
return m_info;
}
json_event_formatter::json_event_formatter(std::shared_ptr<gen_event_filter_factory> json_factory)
: m_output_format(OF_NORMAL), m_json_factory(json_factory)
json_event_formatter::json_event_formatter(json_event_filter_factory &json_factory, std::string &format):
m_format(format),
m_json_factory(json_factory)
{
parse_format();
}
json_event_formatter::~json_event_formatter()
{
}
void json_event_formatter::set_format(output_format of, const std::string &format)
std::string json_event_formatter::tostring(json_event *ev)
{
m_output_format = of;
m_format = format;
parse_format();
}
std::string ret;
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);
if(of == OF_JSON)
std::list<std::pair<std::string, std::string>> resolved;
resolve_tokens(ev, resolved);
for(auto &res : resolved)
{
output = tojson(ev);
return true;
ret += res.second;
}
else
{
std::list<std::pair<std::string, std::string>> resolved;
resolve_format(ev, resolved);
output = "";
for(auto &res : resolved)
{
output += res.second;
}
return true;
}
}
bool json_event_formatter::tostring(gen_event *gevt, std::string &output)
{
return tostring_withformat(gevt, output, m_output_format);
return ret;
}
std::string json_event_formatter::tojson(json_event *ev)
@@ -1563,7 +1428,7 @@ std::string json_event_formatter::tojson(json_event *ev)
std::list<std::pair<std::string, std::string>> resolved;
resolve_format(ev, resolved);
resolve_tokens(ev, resolved);
for(auto &res : resolved)
{
@@ -1578,13 +1443,12 @@ std::string json_event_formatter::tojson(json_event *ev)
return ret.dump();
}
bool json_event_formatter::get_field_values(gen_event *gevt, std::map<std::string, std::string> &fields)
std::map<std::string, std::string> json_event_formatter::tomap(json_event *ev)
{
json_event *ev = static_cast<json_event *>(gevt);
std::map<std::string, std::string> ret;
std::list<std::pair<std::string, std::string>> res;
resolve_format(ev, res);
resolve_tokens(ev, res);
for(auto &r : res)
{
@@ -1595,11 +1459,11 @@ bool json_event_formatter::get_field_values(gen_event *gevt, std::map<std::strin
{
r.second = "<NA>";
}
fields.insert(r);
ret.insert(r);
}
}
return true;
return ret;
}
void json_event_formatter::parse_format()
@@ -1621,7 +1485,7 @@ void json_event_formatter::parse_format()
{
// Skip the %
tformat.erase(0, 1);
json_event_filter_check *chk = (json_event_filter_check *)m_json_factory->new_filtercheck(tformat.c_str());
json_event_filter_check *chk = (json_event_filter_check *)m_json_factory.new_filtercheck(tformat.c_str());
if(!chk)
{
@@ -1657,7 +1521,7 @@ void json_event_formatter::parse_format()
}
}
void json_event_formatter::resolve_format(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved)
void json_event_formatter::resolve_tokens(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved)
{
for(auto tok : m_tokens)
{
@@ -1697,43 +1561,3 @@ void json_event_formatter::resolve_format(json_event *ev, std::list<std::pair<st
}
}
}
gen_event_formatter::output_format json_event_formatter::get_output_format()
{
return m_output_format;
}
json_event_formatter_factory::json_event_formatter_factory(std::shared_ptr<gen_event_filter_factory> json_factory)
: m_output_format(gen_event_formatter::OF_NORMAL), m_json_factory(json_factory)
{
}
json_event_formatter_factory::~json_event_formatter_factory()
{
}
void json_event_formatter_factory::set_output_format(gen_event_formatter::output_format of)
{
m_formatters.clear();
m_output_format = of;
}
std::shared_ptr<gen_event_formatter> json_event_formatter_factory::create_formatter(const std::string &format)
{
auto it = m_formatters.find(format);
if (it != m_formatters.end())
{
return it->second;
}
std::shared_ptr<gen_event_formatter> ret;
ret.reset(new json_event_formatter(m_json_factory));
ret->set_format(m_output_format, format);
m_formatters[format] = ret;
return ret;
}

View File

@@ -57,19 +57,6 @@ protected:
uint64_t m_event_ts;
};
namespace falco_k8s_audit {
//
// Given a raw json object, return a list of k8s audit event
// objects that represent the object. This method handles
// things such as EventList splitting.
//
// Returns true if the json object was recognized as a k8s
// audit event(s), false otherwise.
//
bool parse_k8s_audit_json(nlohmann::json &j, std::list<json_event> &evts, bool top=true);
};
// A class representing an extracted value or a value on the rhs of a
// filter_check. This intentionally doesn't use the same types as
// ppm_events_public.h to take advantage of actual classes instead of
@@ -166,8 +153,8 @@ public:
struct check_info
{
std::string m_name;
std::string m_shortdesc;
std::string m_desc;
std::string m_class_info;
std::list<field_info> m_fields;
};
@@ -189,7 +176,7 @@ public:
// brackets (e.g. ka.image[foo])
size_t parsed_size();
check_info &get_info();
check_info &get_fields();
//
// Allocate a new check of the same type. Must be overridden.
@@ -399,41 +386,29 @@ public:
gen_event_filter_check *new_filtercheck(const char *fldname);
// All defined field names
std::list<gen_event_filter_factory::filter_fieldclass_info> get_fields();
std::list<json_event_filter_check::check_info> &get_fields();
private:
std::list<std::shared_ptr<json_event_filter_check>> m_defined_checks;
std::list<json_event_filter_check::check_info> m_info;
};
class json_event_formatter : public gen_event_formatter
// Unlike the other classes, this does not inherit from a shared class
// that's used both by json events and sinsp events. It might be
// worthwhile, but it would require a lot of additional work to pull
// up functionality into the generic filtercheck class.
class json_event_formatter
{
public:
json_event_formatter(std::shared_ptr<gen_event_filter_factory> factory);
json_event_formatter(json_event_filter_factory &factory, std::string &format);
virtual ~json_event_formatter();
void set_format(output_format of, const std::string &format) override;
bool tostring(gen_event *evt, std::string &output) override;
bool tostring_withformat(gen_event *evt, std::string &output, gen_event_formatter::output_format of) override;
bool get_field_values(gen_event *evt, std::map<std::string, std::string> &fields) override;
output_format get_output_format() override;
std::string tostring(json_event *ev);
std::string tojson(json_event *ev);
std::map<std::string, std::string> tomap(json_event *ev);
// Split the format string into a list of tuples, broken at
// output fields, where each tuple is either a block of text
// from the original format string, or a field value/pair from
// the original format string.
//
// For example, given a format string "some output
// (%proc.name)", this will fill in resolved with 3 tuples:
// - ["", "some output ("]
// - ["proc.name", "nginx"]
// - ["", ")"]
//
// This can be used either to return a resolved output string
// or a map of field name/value pairs.
void resolve_format(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved);
void resolve_tokens(json_event *ev, std::list<std::pair<std::string, std::string>> &resolved);
private:
void parse_format();
@@ -453,8 +428,6 @@ private:
std::shared_ptr<json_event_filter_check> check;
};
gen_event_formatter::output_format m_output_format;
// The original format string
std::string m_format;
@@ -463,25 +436,5 @@ private:
std::list<fmt_token> m_tokens;
// All the filterchecks required to resolve tokens in the format string
std::shared_ptr<gen_event_filter_factory> m_json_factory;
};
class json_event_formatter_factory : public gen_event_formatter_factory
{
public:
json_event_formatter_factory(std::shared_ptr<gen_event_filter_factory> factory);
virtual ~json_event_formatter_factory();
void set_output_format(gen_event_formatter::output_format of) override;
std::shared_ptr<gen_event_formatter> create_formatter(const std::string &format) override;
protected:
// Maps from output string to formatter
std::map<std::string, std::shared_ptr<gen_event_formatter>> m_formatters;
gen_event_formatter::output_format m_output_format;
// All the filterchecks required to resolve tokens in the format string
std::shared_ptr<gen_event_filter_factory> m_json_factory;
json_event_filter_factory &m_json_factory;
};

View File

@@ -1,21 +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.
file(GLOB_RECURSE lua_files ${CMAKE_CURRENT_SOURCE_DIR} *.lua)
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

@@ -1,84 +0,0 @@
#!/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

@@ -18,7 +18,7 @@
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.
While this is based on the sysdig 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.
@@ -196,9 +196,9 @@ local G = {
Identifier = V "idStart" * V "idRest" ^ 0,
Macro = V "idStart" * V "idRest" ^ 0 * -P ".",
Int = digit ^ 1,
ArgString = (alnum + S ",.-_/*?~") ^ 1,
PathString = (alnum + S ",.-_/*?") ^ 1,
PortRangeString = (V "Int" + S ":,") ^ 1,
Index = V "PortRangeString" + V "Int" + V "ArgString",
Index = V "PortRangeString" + V "Int" + V "PathString",
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,

View File

@@ -20,6 +20,7 @@
--]]
local sinsp_rule_utils = require "sinsp_rule_utils"
local compiler = require "compiler"
local yaml = require"lyaml"
@@ -68,7 +69,7 @@ priorities = {
--[[
Take a filter AST and set it up in the libsinsp runtime, using the filter API.
--]]
local function create_filter_obj(node, lua_parser, parent_bool_op)
local function install_filter(node, filter_api_lib, lua_parser, parent_bool_op)
local t = node.type
if t == "BinaryBoolOp" then
@@ -77,84 +78,41 @@ local function create_filter_obj(node, lua_parser, parent_bool_op)
-- never necessary when we have identical successive operators. so we
-- avoid it as a runtime performance optimization.
if (not(node.operator == parent_bool_op)) then
err = filter.nest(lua_parser) -- io.write("(")
if err ~= nil then
return err
end
filter_api_lib.nest(lua_parser) -- io.write("(")
end
err = create_filter_obj(node.left, lua_parser, node.operator)
if err ~= nil then
return err
end
err = filter.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
if err ~= nil then
return err
end
err = create_filter_obj(node.right, lua_parser, node.operator)
if err ~= nil then
return err
end
install_filter(node.left, filter_api_lib, lua_parser, node.operator)
filter_api_lib.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
install_filter(node.right, filter_api_lib, lua_parser, node.operator)
if (not (node.operator == parent_bool_op)) then
err = filter.unnest(lua_parser) -- io.write(")")
if err ~= nil then
return err
end
filter_api_lib.unnest(lua_parser) -- io.write(")")
end
elseif t == "UnaryBoolOp" then
err = filter.nest(lua_parser) --io.write("(")
if err ~= nil then
return err
end
err = filter.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
if err ~= nil then
return err
end
err = create_filter_obj(node.argument, lua_parser)
if err ~= nil then
return err
end
err = filter.unnest(lua_parser) -- io.write(")")
if err ~= nil then
return err
end
filter_api_lib.nest(lua_parser) --io.write("(")
filter_api_lib.bool_op(lua_parser, node.operator) -- io.write(" "..node.operator.." ")
install_filter(node.argument, filter_api_lib, lua_parser)
filter_api_lib.unnest(lua_parser) -- io.write(")")
elseif t == "BinaryRelOp" then
if (node.operator == "in" or
node.operator == "intersects" or
node.operator == "pmatch") then
elements = map(function (el) return el.value end, node.right.elements)
err = filter.rel_expr(lua_parser, node.left.value, node.operator, elements, node.index)
if err ~= nil then
return err
end
filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, elements, node.index)
else
err = filter.rel_expr(lua_parser, node.left.value, node.operator, node.right.value, node.index)
if err ~= nil then
return err
end
filter_api_lib.rel_expr(lua_parser, node.left.value, node.operator, node.right.value, node.index)
end
-- io.write(node.left.value.." "..node.operator.." "..node.right.value)
elseif t == "UnaryRelOp" then
err = filter.rel_expr(lua_parser, node.argument.value, node.operator, node.index)
if err ~= nil then
return err
end
filter_api_lib.rel_expr(lua_parser, node.argument.value, node.operator, node.index)
--io.write(node.argument.value.." "..node.operator)
else
return "Unexpected type in create_filter_obj: "..t
error ("Unexpected type in install_filter: "..t)
end
return nil
end
function set_output(output_format, state)
@@ -293,20 +251,19 @@ function split_lines(rules_content)
end
function get_orig_yaml_obj(rules_lines, row)
idx = row
local t = {}
while (idx <= #rules_lines) do
t[#t + 1] = rules_lines[idx]
idx = idx + 1
local ret = ""
if idx > #rules_lines or rules_lines[idx] == "" or string.sub(rules_lines[idx], 1, 1) == '-' then
break
end
end
t[#t + 1] = ""
local ret = ""
ret = table.concat(t, "\n")
return ret
idx = row
while (idx <= #rules_lines) do
ret = ret..rules_lines[idx].."\n"
idx = idx + 1
if idx > #rules_lines or rules_lines[idx] == "" or string.sub(rules_lines[idx], 1, 1) == '-' then
break
end
end
return ret
end
function get_lines(rules_lines, row, num_lines)
@@ -353,7 +310,7 @@ function build_error_with_context(ctx, err)
return {ret}
end
function validate_exception_item_multi_fields(rules_mgr, source, eitem, context)
function validate_exception_item_multi_fields(eitem, context)
local name = eitem['name']
local fields = eitem['fields']
@@ -372,7 +329,7 @@ function validate_exception_item_multi_fields(rules_mgr, source, eitem, context)
end
end
for k, fname in ipairs(fields) do
if not falco_rules.is_defined_field(rules_mgr, source, fname) then
if not is_defined_filter(fname) then
return false, build_error_with_context(context, "Rule exception item "..name..": field name "..fname.." is not a supported filter field"), warnings
end
end
@@ -383,7 +340,7 @@ function validate_exception_item_multi_fields(rules_mgr, source, eitem, context)
end
end
function validate_exception_item_single_field(rules_mgr, source, eitem, context)
function validate_exception_item_single_field(eitem, context)
local name = eitem['name']
local fields = eitem['fields']
@@ -398,7 +355,7 @@ function validate_exception_item_single_field(rules_mgr, source, eitem, context)
return false, build_error_with_context(context, "Rule exception item "..name..": fields and comps must both be strings"), warnings
end
end
if not falco_rules.is_defined_field(rules_mgr, source, fields) then
if not is_defined_filter(fields) then
return false, build_error_with_context(context, "Rule exception item "..name..": field name "..fields.." is not a supported filter field"), warnings
end
if defined_comp_operators[comps] == nil then
@@ -406,6 +363,37 @@ function validate_exception_item_single_field(rules_mgr, source, eitem, context)
end
end
function is_defined_filter(filter)
if defined_noarg_filters[filter] ~= nil then
return true
else
bracket_idx = string.find(filter, "[", 1, true)
if bracket_idx ~= nil then
subfilter = string.sub(filter, 1, bracket_idx-1)
if defined_arg_filters[subfilter] ~= nil then
return true
end
end
dot_idx = string.find(filter, ".", 1, true)
while dot_idx ~= nil do
subfilter = string.sub(filter, 1, dot_idx-1)
if defined_arg_filters[subfilter] ~= nil then
return true
end
dot_idx = string.find(filter, ".", dot_idx+1, true)
end
end
return false
end
function load_rules_doc(rules_mgr, doc, load_state)
local warnings = {}
@@ -438,29 +426,6 @@ function load_rules_doc(rules_mgr, doc, load_state)
return false, build_error_with_context(v['context'], "Rules require engine version "..v['required_engine_version']..", but engine version is "..falco_rules.engine_version(rules_mgr)), warnings
end
elseif (v['required_plugin_versions']) then
for _, vobj in ipairs(v['required_plugin_versions']) do
if vobj['name'] == nil then
return false, build_error_with_context(v['context'], "required_plugin_versions item must have name property"), warnings
end
if vobj['version'] == nil then
return false, build_error_with_context(v['context'], "required_plugin_versions item must have version property"), warnings
end
-- In the rules yaml, it's a name + version. But it's
-- possible, although unlikely, that a single yaml blob
-- contains multiple docs, withe each doc having its own
-- required_engine_version entry. So populate a map plugin
-- name -> list of required plugin versions.
if load_state.required_plugin_versions[vobj['name']] == nil then
load_state.required_plugin_versions[vobj['name']] = {}
end
table.insert(load_state.required_plugin_versions[vobj['name']], vobj['version'])
end
elseif (v['macro']) then
if (v['macro'] == nil or type(v['macro']) == "table") then
@@ -543,6 +508,10 @@ function load_rules_doc(rules_mgr, doc, load_state)
return false, build_error_with_context(v['context'], "Rule name is empty"), warnings
end
if (v['condition'] == nil and v['exceptions'] == nil) then
return false, build_error_with_context(v['context'], "Rule must have exceptions or condition property"), warnings
end
-- By default, if a rule's condition refers to an unknown
-- filter like evt.type, etc the loader throws an error.
if v['skip-if-unknown-filter'] == nil then
@@ -589,9 +558,9 @@ function load_rules_doc(rules_mgr, doc, load_state)
-- Different handling if the fields property is a single item vs a list
local valid, err
if type(eitem['fields']) == "table" then
valid, err = validate_exception_item_multi_fields(rules_mgr, v['source'], eitem, v['context'])
valid, err = validate_exception_item_multi_fields(eitem, v['context'])
else
valid, err = validate_exception_item_single_field(rules_mgr, v['source'], eitem, v['context'])
valid, err = validate_exception_item_single_field(eitem, v['context'])
end
if valid == false then
@@ -608,82 +577,45 @@ function load_rules_doc(rules_mgr, doc, load_state)
return false, build_error_with_context(v['context'], "Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists"), warnings
end
else
if (v['condition'] == nil and next(v['exceptions']) == nil) then
return false, build_error_with_context(v['context'], "Appended rule must have exceptions or condition property"), warnings
end
if next(v['exceptions']) ~= nil then
for _, eitem in ipairs(v['exceptions']) do
local name = eitem['name']
local fields = eitem['fields']
local comps = eitem['comps']
if eitem['name'] == nil then
if name == nil then
return false, build_error_with_context(v['context'], "Rule exception item must have name property"), warnings
end
-- Seperate case when a exception name is not found
-- This means that a new exception is being appended
local new_exception = true
for _, rex_item in ipairs(state.rules_by_name[v['rule']]['exceptions']) do
if rex_item['name'] == eitem['name'] then
new_exception = false
break
end
-- You can't append exception fields or comps to a rule
if fields ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception fields to existing rule, only values"), warnings
end
if new_exception then
local exceptions = state.rules_by_name[v['rule']]['exceptions']
if eitem['fields'] == nil then
return false, build_error_with_context(v['context'], "Rule exception new item "..eitem['name']..": must have fields property with a list of fields"), warnings
end
if eitem['values'] == nil then
return false, build_error_with_context(v['context'], "Rule exception new item "..eitem['name']..": must have values property with a list of values"), warnings
end
local valid, err
if type(eitem['fields']) == "table" then
valid, err = validate_exception_item_multi_fields(rules_mgr, v['source'], eitem, v['context'])
else
valid, err = validate_exception_item_single_field(rules_mgr, v['source'], eitem, v['context'])
end
if valid == false then
return valid, err, warnings
end
if comps ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception comps to existing rule, only values"), warnings
end
-- Insert the complete exception object
exceptions[#exceptions+1] = eitem
else
-- Appends to existing exception here
-- You can't append exception fields or comps to an existing rule exception
if eitem['fields'] ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception fields to existing rule, only values"), warnings
end
if eitem['comps'] ~= nil then
return false, build_error_with_context(v['context'], "Can not append exception comps to existing rule, only values"), warnings
end
-- You can append values. They are added to the
-- corresponding name, if it exists. If no
-- exception with that name exists, add a
-- warning.
if eitem['values'] ~= nil then
local found=false
for _, reitem in ipairs(state.rules_by_name[v['rule']]['exceptions']) do
if reitem['name'] == eitem['name'] then
found=true
for _, values in ipairs(eitem['values']) do
reitem['values'][#reitem['values'] + 1] = values
end
-- You can append values. They are added to the
-- corresponding name, if it exists. If no
-- exception with that name exists, add a
-- warning.
if eitem['values'] ~= nil then
local found=false
for _, reitem in ipairs(state.rules_by_name[v['rule']]['exceptions']) do
if reitem['name'] == eitem['name'] then
found=true
for _, values in ipairs(eitem['values']) do
reitem['values'][#reitem['values'] + 1] = values
end
end
end
if found == false then
warnings[#warnings + 1] = "Rule "..v['rule'].." with append=true: no set of fields matching name "..eitem['name']
end
end
if found == false then
warnings[#warnings + 1] = "Rule "..v['rule'].." with append=true: no set of fields matching name "..eitem['name']
end
end
end
end
@@ -697,25 +629,14 @@ function load_rules_doc(rules_mgr, doc, load_state)
end
else
local err = nil
for j, field in ipairs({'condition', 'output', 'desc', 'priority'}) do
if (err == nil and v[field] == nil) then
err = build_error_with_context(v['context'], "Rule must have property "..field)
if (v[field] == nil) then
return false, build_error_with_context(v['context'], "Rule must have property "..field), warnings
end
end
-- Handle spacial case where "enabled" flag is defined only
if (err ~= nil) then
if (v['enabled'] == nil) then
return false, err, warnings
else
if state.rules_by_name[v['rule']] == nil then
return false, build_error_with_context(v['context'], "Rule " ..v['rule'].. " has 'enabled' key only, but no rule by that name already exists"), warnings
end
state.rules_by_name[v['rule']]['enabled'] = v['enabled']
end
else
-- Convert the priority-as-string to a priority-as-number now
-- Convert the priority-as-string to a priority-as-number now
v['priority_num'] = priorities[v['priority']]
if v['priority_num'] == nil then
@@ -738,7 +659,6 @@ function load_rules_doc(rules_mgr, doc, load_state)
else
state.skipped_rules_by_name[v['rule']] = v
end
end
end
else
local context = v['context']
@@ -755,69 +675,65 @@ end
-- Populates exfields with all fields used
function build_exception_condition_string_multi_fields(eitem, exfields)
local fields = eitem['fields']
local comps = eitem['comps']
local fields = eitem['fields']
local comps = eitem['comps']
local icond = {}
local icond = "("
icond[#icond + 1] = "("
for i, values in ipairs(eitem['values']) do
local lcount = 0
for i, values in ipairs(eitem['values']) do
if #fields ~= #values then
return nil, "Exception item " .. eitem['name'] .. ": fields and values lists must have equal length"
end
if #fields ~= #values then
return nil, "Exception item "..eitem['name']..": fields and values lists must have equal length"
end
if lcount ~= 0 then
icond[#icond + 1] = " or "
end
lcount = lcount + 1
if icond ~= "(" then
icond=icond.." or "
end
icond[#icond + 1] = "("
icond=icond.."("
for k = 1, #fields do
if k > 1 then
icond[#icond + 1] = " and "
end
local ival = values[k]
local istr = ""
for k=1,#fields do
if k > 1 then
icond=icond.." and "
end
local ival = values[k]
local istr = ""
-- If ival is a table, express it as (titem1, titem2, etc)
if type(ival) == "table" then
istr = "("
for _, item in ipairs(ival) do
if istr ~= "(" then
istr = istr .. ", "
end
istr = istr .. quote_item(item)
end
istr = istr .. ")"
else
-- If the corresponding operator is one that works on lists, possibly add surrounding parentheses.
if defined_list_comp_operators[comps[k]] then
istr = paren_item(ival)
else
-- Quote the value if not already quoted
istr = quote_item(ival)
end
end
-- If ival is a table, express it as (titem1, titem2, etc)
if type(ival) == "table" then
istr = "("
for _, item in ipairs(ival) do
if istr ~= "(" then
istr = istr..", "
end
istr = istr..quote_item(item)
end
istr = istr..")"
else
-- If the corresponding operator is one that works on lists, possibly add surrounding parentheses.
if defined_list_comp_operators[comps[k]] then
istr = paren_item(ival)
else
-- Quote the value if not already quoted
istr = quote_item(ival)
end
end
icond[#icond + 1] = fields[k] .. " " .. comps[k] .. " " .. istr
exfields[fields[k]] = true
end
icond = icond..fields[k].." "..comps[k].." "..istr
exfields[fields[k]] = true
end
icond[#icond + 1] = ")"
end
icond=icond..")"
end
icond[#icond + 1] = ")"
icond = icond..")"
-- Don't return a trivially empty condition string
local ret = table.concat(icond)
if ret == "()" then
return "", nil
end
-- Don't return a trivially empty condition string
if icond == "()" then
icond = ""
end
return ret, nil
return icond, nil
end
@@ -853,10 +769,11 @@ end
-- 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
-- - List of Errors
-- - List of Warnings
function load_rules(rules_content,
function load_rules(sinsp_lua_parser,
json_lua_parser,
rules_content,
rules_mgr,
verbose,
all_events,
@@ -866,7 +783,7 @@ function load_rules(rules_content,
local warnings = {}
local load_state = {lines={}, indices={}, cur_item_idx=0, min_priority=min_priority, required_engine_version=0, required_plugin_versions={}}
local load_state = {lines={}, indices={}, cur_item_idx=0, min_priority=min_priority, required_engine_version=0}
load_state.lines, load_state.indices = split_lines(rules_content)
@@ -887,29 +804,29 @@ function load_rules(rules_content,
row = tonumber(row)
col = tonumber(col)
return false, nil, nil, build_error(load_state.lines, row, 3, docs), warnings
return false, nil, build_error(load_state.lines, row, 3, docs), warnings
end
if docs == nil then
-- An empty rules file is acceptable
return true, load_state.required_engine_version, {}, {}, warnings
return true, load_state.required_engine_version, {}, warnings
end
if type(docs) ~= "table" then
return false, nil, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
return false, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
end
for docidx, doc in ipairs(docs) do
if type(doc) ~= "table" then
return false, nil, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
return false, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml"), warnings
end
-- Look for non-numeric indices--implies that document is not array
-- of objects.
for key, val in pairs(doc) do
if type(key) ~= "number" then
return false, nil, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml array of objects"), warnings
return false, nil, build_error(load_state.lines, 1, 1, "Rules content is not yaml array of objects"), warnings
end
end
@@ -922,7 +839,7 @@ function load_rules(rules_content,
end
if not res then
return res, nil, nil, errors, warnings
return res, nil, errors, warnings
end
end
@@ -963,7 +880,13 @@ function load_rules(rules_content,
local status, ast = compiler.compile_macro(v['condition'], state.macros, state.lists)
if status == false then
return false, nil, nil, build_error_with_context(v['context'], ast), warnings
return false, nil, build_error_with_context(v['context'], ast), warnings
end
if v['source'] == "syscall" then
if not all_events then
sinsp_rule_utils.check_for_ignored_syscalls_events(ast, 'macro', v['condition'])
end
end
state.macros[v['macro']] = {["ast"] = ast.filter.value, ["used"] = false}
@@ -989,7 +912,7 @@ function load_rules(rules_content,
end
if err ~= nil then
return false, nil, nil, build_error_with_context(v['context'], err), warnings
return false, nil, build_error_with_context(v['context'], err), warnings
end
if icond ~= "" then
@@ -1010,23 +933,42 @@ function load_rules(rules_content,
warn_evttypes = v['warn_evttypes']
end
local status, filter_ast = compiler.compile_filter(v['rule'], v['compile_condition'],
state.macros, state.lists)
local status, filter_ast, filters = compiler.compile_filter(v['rule'], v['compile_condition'],
state.macros, state.lists)
if status == false then
return false, nil, nil, build_error_with_context(v['context'], filter_ast), warnings
return false, nil, build_error_with_context(v['context'], filter_ast), warnings
end
local evtttypes = {}
local syscallnums = {}
if v['source'] == "syscall" then
if not all_events then
sinsp_rule_utils.check_for_ignored_syscalls_events(filter_ast, 'rule', v['rule'])
end
evttypes, syscallnums = sinsp_rule_utils.get_evttypes_syscalls(name, filter_ast, v['compile_condition'], warn_evttypes, verbose)
end
-- If a filter in the rule doesn't exist, either skip the rule
-- or raise an error, depending on the value of
-- skip-if-unknown-filter.
for filter, _ in pairs(filters) do
if not is_defined_filter(filter) then
msg = "rule \""..v['rule'].."\": contains unknown filter "..filter
warnings[#warnings + 1] = msg
if not v['skip-if-unknown-filter'] then
return false, nil, build_error_with_context(v['context'], msg), warnings
else
print("Skipping "..msg)
goto next_rule
end
end
end
if (filter_ast.type == "Rule") then
valid = falco_rules.is_source_valid(rules_mgr, v['source'])
if valid == false then
msg = "Rule "..v['rule']..": warning (unknown-source): unknown source "..v['source']..", skipping"
warnings[#warnings + 1] = msg
goto next_rule
end
state.n_rules = state.n_rules + 1
state.rules_by_idx[state.n_rules] = v
@@ -1041,30 +983,15 @@ function load_rules(rules_content,
if (v['tags'] == nil) then
v['tags'] = {}
end
if v['source'] == "syscall" then
install_filter(filter_ast.filter.value, filter, sinsp_lua_parser)
-- Pass the filter and event types back up
falco_rules.add_filter(rules_mgr, v['rule'], evttypes, syscallnums, v['tags'])
lua_parser = falco_rules.create_lua_parser(rules_mgr, v['source'])
local err = create_filter_obj(filter_ast.filter.value, lua_parser)
if err ~= nil then
elseif v['source'] == "k8s_audit" then
install_filter(filter_ast.filter.value, k8s_audit_filter, json_lua_parser)
-- If a rule has a property skip-if-unknown-filter: true,
-- and the error is about an undefined field, print a
-- message but continue.
if v['skip-if-unknown-filter'] == true and string.find(err, "filter_check called with nonexistent field") ~= nil then
msg = "Rule "..v['rule']..": warning (unknown-field):"
warnings[#warnings + 1] = msg
else
msg = "Rule "..v['rule']..": error "..err
return false, nil, nil, build_error_with_context(v['context'], msg), warnings
end
else
num_evttypes = falco_rules.add_filter(rules_mgr, lua_parser, v['rule'], v['source'], v['tags'])
if v['source'] == "syscall" and (num_evttypes == 0 or num_evttypes > 100) then
if warn_evttypes == true then
msg = "Rule "..v['rule']..": warning (no-evttype):\n".." matches too many evt.type values.\n".." This has a significant performance penalty."
warnings[#warnings + 1] = msg
end
end
falco_rules.add_k8s_audit_filter(rules_mgr, v['rule'], v['tags'])
end
-- Rule ASTs are merged together into one big AST, with "OR" between each
@@ -1115,12 +1042,14 @@ function load_rules(rules_content,
-- Ensure that the output field is properly formatted by
-- creating a formatter from it. Any error will be thrown
-- up to the top level.
local err = falco_rules.is_format_valid(rules_mgr, v['source'], v['output'])
if err ~= nil then
return false, nil, nil, build_error_with_context(v['context'], err), warnings
local err, formatter = formats.formatter(v['source'], v['output'])
if err == nil then
formats.free_formatter(v['source'], formatter)
else
return false, nil, build_error_with_context(v['context'], err), warnings
end
else
return false, nil, nil, build_error_with_context(v['context'], "Unexpected type in load_rule: "..filter_ast.type), warnings
return false, nil, build_error_with_context(v['context'], "Unexpected type in load_rule: "..filter_ast.type), warnings
end
::next_rule::
@@ -1143,7 +1072,7 @@ function load_rules(rules_content,
io.flush()
return true, load_state.required_engine_version, load_state.required_plugin_versions, {}, warnings
return true, load_state.required_engine_version, {}, warnings
end
local rule_fmt = "%-50s %s"
@@ -1227,7 +1156,7 @@ function on_event(rule_id)
error ("rule_loader.on_event(): could not find rule by name: ", rule.rule)
end
return rule.rule, rule.priority_num, output, combined_rule.exception_fields, rule.tags
return rule.rule, rule.priority_num, output, combined_rule.exception_fields
end
function print_stats()

View File

@@ -0,0 +1,199 @@
-- Copyright (C) 2019 The Falco Authors.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local parser = require("parser")
local sinsp_rule_utils = {}
function sinsp_rule_utils.check_for_ignored_syscalls_events(ast, filter_type, source)
function check_syscall(val)
if ignored_syscalls[val] then
error("Ignored syscall \""..val.."\" in "..filter_type..": "..source)
end
end
function check_event(val)
if ignored_events[val] then
error("Ignored event \""..val.."\" in "..filter_type..": "..source)
end
end
function cb(node)
if node.left.type == "FieldName" and
(node.left.value == "evt.type" or
node.left.value == "syscall.type") then
if (node.operator == "in" or
node.operator == "intersects" or
node.operator == "pmatch") then
for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then
if node.left.value == "evt.type" then
check_event(v.value)
else
check_syscall(v.value)
end
end
end
else
if node.right.type == "BareString" then
if node.left.value == "evt.type" then
check_event(node.right.value)
else
check_syscall(node.right.value)
end
end
end
end
end
parser.traverse_ast(ast, {BinaryRelOp=1}, cb)
end
-- Examine the ast and find the event types/syscalls for which the
-- rule should run. All evt.type references are added as event types
-- up until the first "!=" binary operator or unary not operator. If
-- no event type checks are found afterward in the rule, the rule is
-- considered optimized and is associated with the event type(s).
--
-- Otherwise, the rule is associated with a 'catchall' category and is
-- run for all event types/syscalls. (Also, a warning is printed).
--
function sinsp_rule_utils.get_evttypes_syscalls(name, ast, source, warn_evttypes, verbose)
local evttypes = {}
local syscallnums = {}
local evtnames = {}
local found_event = false
local found_not = false
local found_event_after_not = false
function cb(node)
if node.type == "UnaryBoolOp" then
if node.operator == "not" then
found_not = true
end
else
if node.operator == "!=" then
found_not = true
end
if node.left.type == "FieldName" and node.left.value == "evt.type" then
found_event = true
if found_not then
found_event_after_not = true
end
if (node.operator == "in" or
node.operator == "intersects" or
node.operator == "pmatch") then
for i, v in ipairs(node.right.elements) do
if v.type == "BareString" then
-- The event must be a known event
if events[v.value] == nil and syscalls[v.value] == nil then
error("Unknown event/syscall \""..v.value.."\" in filter: "..source)
end
evtnames[v.value] = 1
if events[v.value] ~= nil then
for id in string.gmatch(events[v.value], "%S+") do
evttypes[id] = 1
end
end
if syscalls[v.value] ~= nil then
for id in string.gmatch(syscalls[v.value], "%S+") do
syscallnums[id] = 1
end
end
end
end
else
if node.right.type == "BareString" then
-- The event must be a known event
if events[node.right.value] == nil and syscalls[node.right.value] == nil then
error("Unknown event/syscall \""..node.right.value.."\" in filter: "..source)
end
evtnames[node.right.value] = 1
if events[node.right.value] ~= nil then
for id in string.gmatch(events[node.right.value], "%S+") do
evttypes[id] = 1
end
end
if syscalls[node.right.value] ~= nil then
for id in string.gmatch(syscalls[node.right.value], "%S+") do
syscallnums[id] = 1
end
end
end
end
end
end
end
parser.traverse_ast(ast.filter.value, {BinaryRelOp=1, UnaryBoolOp=1} , cb)
if not found_event then
if warn_evttypes == true then
io.stderr:write("Rule "..name..": warning (no-evttype):\n")
io.stderr:write(source.."\n")
io.stderr:write(" did not contain any evt.type restriction, meaning it will run for all event types.\n")
io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n")
end
evttypes = {}
syscallnums = {}
evtnames = {}
end
if found_event_after_not then
if warn_evttypes == true then
io.stderr:write("Rule "..name..": warning (trailing-evttype):\n")
io.stderr:write(source.."\n")
io.stderr:write(" does not have all evt.type restrictions at the beginning of the condition,\n")
io.stderr:write(" or uses a negative match (i.e. \"not\"/\"!=\") for some evt.type restriction.\n")
io.stderr:write(" This has a performance penalty, as the rule can not be limited to specific event types.\n")
io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
io.stderr:write(" replacing negative matches with positive matches if possible.\n")
end
evttypes = {}
syscallnums = {}
evtnames = {}
end
evtnames_only = {}
local num_evtnames = 0
for name, dummy in pairs(evtnames) do
table.insert(evtnames_only, name)
num_evtnames = num_evtnames + 1
end
if num_evtnames == 0 then
table.insert(evtnames_only, "all")
end
table.sort(evtnames_only)
if verbose then
io.stderr:write("Event types/Syscalls for rule "..name..": "..table.concat(evtnames_only, ",").."\n")
end
return evttypes, syscallnums
end
return sinsp_rule_utils

View File

@@ -30,32 +30,26 @@ extern "C" {
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},
{"add_k8s_audit_filter", &falco_rules::add_k8s_audit_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,
falco_rules::falco_rules(sinsp* inspector,
falco_engine *engine,
lua_State *ls)
: m_engine(engine),
: m_inspector(inspector),
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;
m_sinsp_lua_parser = new lua_parser(engine->sinsp_factory(), m_ls, "filter");
m_json_lua_parser = new lua_parser(engine->json_factory(), m_ls, "k8s_audit_filter");
}
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)
@@ -77,51 +71,12 @@ 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_isstring(ls, -4) ||
! lua_istable(ls, -3) ||
! lua_istable(ls, -2) ||
! lua_istable(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to add_filter()");
@@ -129,9 +84,31 @@ int falco_rules::add_filter(lua_State *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);
const char *rulec = lua_tostring(ls, -4);
set<uint32_t> evttypes;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -4) != 0) {
// key is at index -2, value is at index
// -1. We want the keys.
evttypes.insert(luaL_checknumber(ls, -2));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
set<uint32_t> syscalls;
lua_pushnil(ls); /* first key */
while (lua_next(ls, -3) != 0) {
// key is at index -2, value is at index
// -1. We want the keys.
syscalls.insert(luaL_checknumber(ls, -2));
// Remove value, keep key for next iteration
lua_pop(ls, 1);
}
set<string> tags;
@@ -145,25 +122,61 @@ int falco_rules::add_filter(lua_State *ls)
lua_pop(ls, 1);
}
size_t num_evttypes = lp->filter()->evttypes().size();
std::string rule = rulec;
rules->add_filter(rule, evttypes, syscalls, tags);
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());
return 0;
}
int falco_rules::add_k8s_audit_filter(lua_State *ls)
{
if (! lua_islightuserdata(ls, -3) ||
! lua_isstring(ls, -2) ||
! lua_istable(ls, -1))
{
lua_pushstring(ls, "Invalid arguments passed to add_k8s_audit_filter()");
lua_error(ls);
}
delete lp;
falco_rules *rules = (falco_rules *) lua_topointer(ls, -3);
const char *rulec = lua_tostring(ls, -2);
lua_pushnumber(ls, num_evttypes);
return 1;
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);
}
std::string rule = rulec;
rules->add_k8s_audit_filter(rule, tags);
return 0;
}
void falco_rules::add_filter(std::shared_ptr<gen_event_filter> filter, string &rule, string &source, set<string> &tags)
void falco_rules::add_filter(string &rule, set<uint32_t> &evttypes, set<uint32_t> &syscalls, set<string> &tags)
{
m_engine->add_filter(filter, rule, source, tags);
// While the current rule was being parsed, a sinsp_filter
// object was being populated by lua_parser. Grab that filter
// and pass it to the engine.
sinsp_filter *filter = (sinsp_filter *) m_sinsp_lua_parser->get_filter(true);
m_engine->add_sinsp_filter(rule, evttypes, syscalls, tags, filter);
}
void falco_rules::add_k8s_audit_filter(string &rule, set<string> &tags)
{
// While the current rule was being parsed, a sinsp_filter
// object was being populated by lua_parser. Grab that filter
// and pass it to the engine.
json_event_filter *filter = (json_event_filter *) m_json_lua_parser->get_filter(true);
m_engine->add_k8s_audit_filter(rule, tags, filter);
}
int falco_rules::enable_rule(lua_State *ls)
@@ -206,128 +219,6 @@ int falco_rules::engine_version(lua_State *ls)
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;
@@ -353,52 +244,204 @@ static std::list<std::string> get_lua_table_values(lua_State *ls, int idx)
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)
uint64_t &required_engine_version)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
{
// Create a table containing all events, so they can
// be mapped to event ids.
sinsp_evttables* einfo = m_inspector->get_event_info_tables();
const struct ppm_event_info* etable = einfo->m_event_info;
const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table;
map<string,string> events_by_name;
for(uint32_t j = 0; j < PPM_EVENT_MAX; j++)
{
auto it = events_by_name.find(etable[j].name);
if (it == events_by_name.end()) {
events_by_name[etable[j].name] = to_string(j);
} else {
string cur = it->second;
cur += " ";
cur += to_string(j);
events_by_name[etable[j].name] = cur;
}
}
lua_newtable(m_ls);
for( auto kv : events_by_name)
{
lua_pushstring(m_ls, kv.first.c_str());
lua_pushstring(m_ls, kv.second.c_str());
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_events.c_str());
map<string,string> syscalls_by_name;
for(uint32_t j = 0; j < PPM_SC_MAX; j++)
{
auto it = syscalls_by_name.find(stable[j].name);
if (it == syscalls_by_name.end())
{
syscalls_by_name[stable[j].name] = to_string(j);
}
else
{
string cur = it->second;
cur += " ";
cur += to_string(j);
syscalls_by_name[stable[j].name] = cur;
}
}
lua_newtable(m_ls);
for( auto kv : syscalls_by_name)
{
lua_pushstring(m_ls, kv.first.c_str());
lua_pushstring(m_ls, kv.second.c_str());
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_syscalls.c_str());
// Create a table containing the syscalls/events that
// are ignored by the kernel module. load_rules will
// return an error if any rule references one of these
// syscalls/events.
lua_newtable(m_ls);
for(uint32_t j = 0; j < PPM_EVENT_MAX; j++)
{
if(etable[j].flags & EF_DROP_SIMPLE_CONS)
{
lua_pushstring(m_ls, etable[j].name);
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
}
lua_setglobal(m_ls, m_lua_ignored_events.c_str());
lua_newtable(m_ls);
for(uint32_t j = 0; j < PPM_SC_MAX; j++)
{
if(stable[j].flags & EF_DROP_SIMPLE_CONS)
{
lua_pushstring(m_ls, stable[j].name);
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
}
lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str());
vector<const filter_check_info*> fc_plugins;
sinsp::get_filtercheck_fields_info(&fc_plugins);
set<string> no_argument_filters;
set<string> argument_filters;
for(uint32_t j = 0; j < fc_plugins.size(); j++)
{
const filter_check_info* fci = fc_plugins[j];
if(fci->m_flags & filter_check_info::FL_HIDDEN)
{
continue;
}
for(int32_t k = 0; k < fci->m_nfields; k++)
{
const filtercheck_field_info* fld = &fci->m_fields[k];
if(fld->m_flags & EPF_TABLE_ONLY ||
fld->m_flags & EPF_PRINT_ONLY)
{
continue;
}
// Some filters can work with or without an argument
std::set<string> flexible_filters = {
"proc.aname",
"proc.apid"
};
if(fld->m_flags & EPF_REQUIRES_ARGUMENT ||
flexible_filters.find(fld->m_name) != flexible_filters.end())
{
argument_filters.insert(fld->m_name);
}
if(!(fld->m_flags & EPF_REQUIRES_ARGUMENT) ||
flexible_filters.find(fld->m_name) != flexible_filters.end())
{
no_argument_filters.insert(fld->m_name);
}
}
}
for(auto &chk_field : m_engine->json_factory().get_fields())
{
for(auto &field : chk_field.m_fields)
{
switch(field.m_idx_mode)
{
case json_event_filter_check::IDX_REQUIRED:
argument_filters.insert(field.m_name);
break;
case json_event_filter_check::IDX_ALLOWED:
argument_filters.insert(field.m_name);
no_argument_filters.insert(field.m_name);
break;
case json_event_filter_check::IDX_NONE:
no_argument_filters.insert(field.m_name);
break;
default:
break;
}
}
}
// Create tables containing all filtercheck
// names. They are split into names that require
// arguments and ones that do not.
lua_newtable(m_ls);
for(auto &field : argument_filters)
{
lua_pushstring(m_ls, field.c_str());
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_defined_arg_filters.c_str());
lua_newtable(m_ls);
for(auto &field : no_argument_filters)
{
lua_pushstring(m_ls, field.c_str());
lua_pushnumber(m_ls, 1);
lua_settable(m_ls, -3);
}
lua_setglobal(m_ls, m_lua_defined_noarg_filters.c_str());
lua_pushlightuserdata(m_ls, m_sinsp_lua_parser);
lua_pushlightuserdata(m_ls, m_json_lua_parser);
lua_pushstring(m_ls, rules_content.c_str());
lua_pushlightuserdata(m_ls, this);
lua_pushboolean(m_ls, (verbose ? 1 : 0));
@@ -406,7 +449,7 @@ void falco_rules::load_rules(const string &rules_content,
lua_pushstring(m_ls, extra.c_str());
lua_pushboolean(m_ls, (replace_container_info ? 1 : 0));
lua_pushnumber(m_ls, min_priority);
if(lua_pcall(m_ls, 7, 5, 0) != 0)
if(lua_pcall(m_ls, 9, 4, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
@@ -418,12 +461,10 @@ void falco_rules::load_rules(const string &rules_content,
// 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);
bool successful = lua_toboolean(m_ls, -4);
required_engine_version = lua_tonumber(m_ls, -3);
std::list<std::string> errors = get_lua_table_values(m_ls, -2);
std::list<std::string> warnings = get_lua_table_values(m_ls, -1);
@@ -492,4 +533,6 @@ void falco_rules::describe_rule(std::string *rule)
falco_rules::~falco_rules()
{
delete m_sinsp_lua_parser;
delete m_json_lua_parser;
}

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