This page documents the YAML language consumed by event-generator suite run and
event-generator suite test. For an interactive, schema-driven view of the same
material, use event-generator suite explain.
The language is layered. The simplest description specifies a single test that performs a single system call; from there, the user can incrementally add a process chain, a containerised context, resources that expose bindable fields, template parameters that generate multiple tests from a single block, and an expected outcome that is matched against Falco alerts. This page is organized in the same order: each section assumes only what the previous sections have introduced.
A minimal valid description is like the following:
tests:
- name: test_steps_syscall_dup
description: "Testing dup system call"
runner: HostRunner
steps:
- type: syscall
name: d1
syscall: dup
args:
oldFd: 1This declares one test, called test_steps_syscall_dup, that runs the dup system call on file descriptor 1. The test
is not associated with any rule and therefore can be loaded by run, but not by test.
A topic-focused sample is available for each main feature documented below; see the samples/ folder
and its README for the full corpus.
The document root is an object with a single property:
| Property | Required | Description |
|---|---|---|
tests |
yes | A non-empty array of test descriptions. Each entry is documented in Test fields. |
Every entry in tests is an object with the following properties.
| Property | Required | Type | Description |
|---|---|---|---|
name |
yes | string | The test name. It must be unique across all descriptions loaded in the same invocation. |
runner |
yes | string | The type of test runner. Currently the only supported value is HostRunner. |
rule |
for test |
string | The name of the Falco rule that this test is expected to trigger. Required when running the test through event-generator suite test, and used to group tests into suites and to match alerts back to the originating test. Optional under event-generator suite run: tests without a rule are grouped under a single anonymous suite identified by the empty string. |
description |
no | string | A free-form human description. Surfaced in reports and logs. |
context |
no | object | The execution context. Specifies a custom process chain and, optionally, a container to host that chain. See Test context. |
before |
no | string | A bash script that is run before the resources are created. Useful for one-off setup steps that do not deserve their own resource type. See samples/scripts.yaml. |
after |
no | string | A bash script that is run after the steps complete, regardless of success. Useful for one-off cleanup. See samples/scripts.yaml. |
resources |
no | array | The list of test resources. Resources are created in order of appearance and the values they expose can be bound to step arguments. See Resources. |
steps |
no | array | The list of test steps. Steps are executed in order of appearance, after every resource has been created. See Steps. |
expectedOutcome |
no | object | The Falco alert that the test is expected to produce. Only taken into account by event-generator suite test. See Expected outcome. |
cases |
no | array | A list of test case specifications. When present, the test is treated as a template and one concrete test is generated per case. See Templates and cases. |
The execution order, for a single test, is:
- Build the process chain declared in
context.processes(inside the container declared incontext.container, if requested). - Run the
beforebash script on the leaf process. - Create the resources in
resources, in order. - Execute the steps in
steps, in order. - Run the
afterbash script on the leaf process. - (Only under
test) wait for matching Falco alerts and produce the test report.
The context object configures the process chain that runs the test. It has two optional properties: processes,
which describes a chain of one or more processes, and container, which wraps the chain inside a container.
context:
processes:
- args: "arg1 arg2"
name: "proc0"
exe: "arg0"
user: user1
- user: user2
- user: root
capabilities: "cap_net_admin,cap_net_bind_service,cap_chown=ep"Each entry of processes describes one process in a chain in which each process is spawned by the previous one. The
last entry is the leaf process, and it is the only one that is monitored: the actual resources and steps run inside
the leaf. All preceding entries exist solely to give the leaf a controlled set of ancestors (for instance, to satisfy
rules that look at proc.aname or proc.ppid).
Each process context supports the following properties (all optional):
| Property | Type | Description |
|---|---|---|
exePath |
string | The executable path. Defaults to a randomly generated path under /tmp. The directory hierarchy leading to exePath is created on the fly when missing and removed when the process resource is released (see PR #278). |
args |
string | A space-separated list of command-line arguments. If a single argument contains spaces, the whole argument must be quoted. Defaults to the empty string. |
exe |
string | The argument in position 0 (argv[0]). Defaults to name if specified, otherwise to filepath.Base(exePath). |
name |
string | The process name as visible to the kernel. Defaults to filepath.Base(exePath). |
env |
map of strings | Additional environment variables, on top of the defaults inherited from the parent process. Each key is the variable name and the corresponding value is the variable value. |
user |
string | The name of the user that runs the process. Defaults to the current user. If the user does not exist on the system, it is created before the test and deleted after the test, so the description does not require pre-existing accounts. |
capabilities |
string | The capability set assigned to the process. The syntax follows cap_from_text(3). Defaults to all=iep (full capability set, inheritable, effective, permitted). |
Capabilities can be set only on the leaf process. Specifying capabilities on a non-leaf entry is rejected by the
description loader with an explicit error: the chain validation enforces that constraint before any action is run.
If context.processes is omitted entirely (and no context.container is declared either), the loader injects a single
default process so that there is always at least one process in the chain. This is why descriptions that do not care
about the chain (such as the minimal dup example at the top of this page) work without an explicit context.
See samples/context_processes.yaml for a runnable example.
context:
container:
image: image-name
name: container-name
env:
MY_VAR: fooWhen context.container is present, the entire process chain is run inside a container, instead of directly on the
host. The image used to spawn the container is the one declared by --container-base-image (default
docker.io/falcosecurity/event-generator:latest): the runtime pulls it according to --container-image-pull-policy,
and if the test's image field is different from the base image name the pulled image is given that name as an
additional Docker tag. The running container is named after name. The container daemon is reached over the Unix
socket specified by --container-runtime-unix-socket (a Docker-compatible daemon, see PR
#282).
| Property | Type | Description |
|---|---|---|
image |
string | The tag to apply to the base image before spawning the container. Defaults to the base image name. |
name |
string | The name of the running container. Defaults to event-generator. |
env |
map of strings | Additional environment variables to inject, on top of the defaults the container needs to find the description and the test UID. Each key is the variable name and the corresponding value is the variable value. |
See samples/context_container.yaml for a runnable example.
A resource creates one or more system resources before the steps run, and exposes some of their fields so that steps can refer to them via field bindings. Resources are created in order of appearance and torn down after the test finishes.
Every resource has a type and a name. The name must be unique across all resources and steps of the same test (see
PR #262); steps reference resources by name when binding
to their exposed fields.
Three resource types are currently supported:
clientServer(see clientServer)fd(see fd, with eight subtypes)process(see process, see PR #241)
resources:
- type: clientServer
name: cs1
l4Proto: tcp4
address: 11.0.0.1:80Sets up a client and a server, tunes the underlying network infrastructure (loopback addresses, ephemeral routes, sockets) so that they can talk to each other, and exposes both endpoints. For connection-oriented protocols, the client is automatically connected to the server.
| Property | Type | Description |
|---|---|---|
l4Proto |
enum | One of udp4, udp6, tcp4, tcp6, unix. |
address |
string | The endpoint exposed by the server, as accepted by net.SplitHostPort, or the empty string when l4Proto is unix. |
Exposed fields:
| Path | Description |
|---|---|
<name>.client.fd |
The client file descriptor. |
<name>.server.fd |
The server file descriptor. |
See samples/resources_clientserver.yaml for a runnable example covering
every supported transport protocol.
resources:
- type: fd
name: f1
subtype: file
filePath: /tmp/foo.txtSets up one or more file descriptors. The actual shape depends on subtype:
| Subtype | Extra fields | Exposed fields |
|---|---|---|
file |
filePath (required, string) |
<name>.fd |
directory |
dirPath (required, string) |
<name>.fd |
pipe |
none | <name>.readFd, <name>.writeFd |
event |
none | <name>.fd |
signalfd |
none | <name>.fd |
eventpoll |
none | <name>.fd |
inotify |
none | <name>.fd |
memfd |
fileName (required, string) |
<name>.fd |
For the file and directory subtypes, any missing directory in the supplied path is created on the fly and removed
when the resource is released (see PR #279). This means
that dirPath: /tmp/foo/bar works without a pre-existing /tmp/foo/ directory.
See samples/resources_fd.yaml for a runnable example covering every supported subtype.
resources:
- type: process
name: p1Spawns a process and exposes its PID. This is the resource that enables tests for rules involving process
manipulation, such as kill or ptrace. The properties accepted are a subset of those accepted by
context.processes entries (exePath, args, exe, procName, env).
Exposed fields:
| Path | Description |
|---|---|
<name>.pid |
The process identifier. |
See samples/resources_process.yaml for a runnable example.
A step performs one action in the context of a test. Currently the only supported step type is syscall. Every step
has a type and a name; the name must be unique across resources and steps in the same test (see PR
#262) and is used to reference the step's exposed values
from later steps via field bindings.
steps:
- type: syscall
name: dup1
syscall: dup2
args:
oldFd: "${cs1.client.fd}"
newFd: 0A syscall step runs the named system call with the supplied arguments. After the call has been issued, the step
exposes both its arguments and (for most syscalls) the return value for binding by other steps.
The supported system calls, their arguments and their exposed fields are listed below. Arguments marked (o) are
optional. Exposed fields marked † are bindOnly: their value can be provided to the step only via a binding
expression (a literal value is rejected). Numeric arguments can be specified either as integers or as strings of
|-separated symbolic constants, which is the form most readable for flag arguments. For instance
flags: "O_CREAT|O_CLOEXEC|O_RDWR" is equivalent to the integer that would be produced by OR-ing the same flags in C.
syscall |
Arguments | Exposed fields |
|---|---|---|
write |
fd, buffer(o), len(o) |
fd, buffer, len, ret |
read |
fd, buffer(o), len(o) |
fd, buffer, len, ret |
open |
pathname, flags, mode(o) |
pathname, flags, mode, ret |
openat |
dirFd, pathname, flags, mode(o) |
dirFd†, pathname, flags, mode, ret |
openat2 |
dirFd, pathname, how(o) |
dirFd†, pathname, how.flags, how.mode, how.resolve, ret |
symlink |
target, linkPath |
target, linkPath |
symlinkat |
target, newDirFd, linkPath |
target, newDirFd†, linkPath |
link |
oldPath, newPath |
oldPath, newPath |
linkat |
oldDirFd, oldPath, newDirFd, newPath, flags(o) |
oldDirFd†, oldPath, newDirFd†, newPath, flags |
init_module |
moduleImage, paramValues(o) |
moduleImage, paramValues |
finit_module |
fd, paramValues(o), flags(o) |
fd†, paramValues, flags |
dup |
oldFd |
oldFd, ret |
dup2 |
oldFd, newFd |
oldFd, newFd, ret |
dup3 |
oldFd, newFd, flags(o) |
oldFd, newFd, flags, ret |
connect |
fd, address |
fd, address |
socket |
domain, type, protocol |
domain, type, protocol, ret |
sendto |
fd, buf, flags, destAddr, len(o) |
fd†, buf, len, flags, destAddr |
kill |
pid, sig |
pid†, sig |
For the authoritative, per-syscall list of arguments and their exposed fields, run:
event-generator suite explain tests.steps{type=syscall}{syscall=<SYSCALL>}A field binding lets a step's argument refer to a value exposed by a preceding resource or step, instead of hard-coding it. The syntax is:
${<resourceOrStepName>.<pathToExposedField>}
For example, after declaring a clientServer resource named cs1, the client file descriptor can be passed as the
old fd of a dup2 call without knowing the actual integer:
resources:
- type: clientServer
name: cs1
l4Proto: tcp4
address: 11.0.0.1:80
steps:
- type: syscall
name: dup1
syscall: dup2
args:
oldFd: "${cs1.client.fd}"
newFd: 0Bindings are resolved at runtime, after the producing resource or step has run. A binding to a value not yet produced is rejected at load time, so forward references are impossible.
A test that declares a cases array is a template: the actual concrete tests are generated by substituting template
parameters with the values provided in the cases. A template parameter is referenced in any string value in the
description with the syntax %{item.<keyName>}. The cases array provides one or more case specifications, each with a
strategy:
vector: each key invaluesmaps to a single value. One concrete test is generated per case.matrix: each key invaluesmaps to a list of values. The combinations of all lists are used to generate one concrete test per combination, the count being the product of the lists' cardinalities.
A template is not instantiated if it does not declare any case. Every case specification within the same template must declare the same set of value keys, so that every concrete test has a value for every placeholder.
The following description (taken from PR #266) declares a
single template that expands to thirteen concrete tests: one from the vector case and twelve from the matrix case
(3 * 2 * 1 * 2):
tests:
- name: template_feat_test
description: "Testing the test template feature"
runner: HostRunner
resources:
- type: clientServer
name: cs1
l4Proto: "%{ item.l4Proto }"
address: "%{ item.address }"
steps:
- type: syscall
name: d1
syscall: "%{ item.syscall }"
args:
oldFd: "${ cs1.client.fd }"
newFd: "%{ item.newFd }"
cases:
- strategy: vector
values:
newFd: 0
l4Proto: tcp4
syscall: dup2
address: "10.0.0.1:4000"
- strategy: matrix
values:
newFd: [ 11, 12, 13 ]
l4Proto: [ "tcp4", "udp4" ]
address: [ "10.0.0.1:4000" ]
syscall: [ "dup2", "dup3" ]To keep names unique within the description, the loader suffixes every generated test's name with
_testCase#<index>. The index is zero-based and counts across every case the template expands to, in the order the
cases array would produce them: each vector spec contributes one case, each matrix spec contributes its
combinations, and indices accumulate from one spec to the next. The template above therefore produces tests named
template_feat_test_testCase#0 (the vector case) through template_feat_test_testCase#12 (the last matrix
combination). Only name is mangled; the rule field is left untouched, so all generated tests still belong to the
same test suite.
Each generated test carries the information about its originating case, so reports can tell which value combination produced which result (see PR #291).
See samples/templates.yaml for a runnable example.
expectedOutcome:
source: "syscall"
priority: "WARNING"
outputFields:
proc.name: "httpd"The expectedOutcome section declares the Falco alert that the test is expected to produce. Only
event-generator suite test consumes it: the run command ignores it. The matching
is lenient: a Falco alert matches the expected outcome if it carries the right rule name (taken from the test's rule
field) and matches every key explicitly declared under expectedOutcome. Fields that are not declared are not matched
against.
| Property | Type | Description |
|---|---|---|
source |
string | The Falco event source (for example syscall). |
hostname |
string | The Falco event hostname. |
priority |
string | The Falco event priority (for example WARNING). |
outputFields |
map of strings | Output fields attached to the Falco event. Each entry is matched as a string equality against the corresponding field in the alert. |
A common idiom in the samples is the empty expectedOutcome: {}. It matches any alert produced for the test's rule, so
it is the lightest form of verification: useful when the test is supposed to make the rule fire and the user does not
care about the details.
See samples/expected_outcome.yaml for a runnable example demonstrating both the
strict and the loose forms.
The following rules are enforced by the loader before any action is run. Failing any of them produces an explicit error naming the offending element.
- The name of every test must be present.
- The runner of every test must be present and must be one of the supported runners (currently only
HostRunner). - Names of resources and steps within the same test must be unique.
- Capabilities can only be specified on the leaf process of a process chain.
- Under
event-generator suite test, every test must specify a non-emptyrule. - Field bindings must reference an existing resource or step, and that resource or step must appear before the binding is consumed.
- No
envblock (undercontext.container,context.processes[], orresources[type=process]) may declare a key that starts withFALCO_EVENT_GENERATOR_or equalsSUITE. The event-generator uses those names to propagate the test description, test ID, baggage and the recursive sub-command marker to child processes and containers; a user-supplied value would shadow them and silently break test ID propagation.
- Run
event-generator suite explaininteractively to inspect any portion of the language, against the exact version of the binary in use. - Read the
samples/folder for working descriptions that exercise most of the features documented above.