Basic Elements of Falco Rules

Understand Falco Rules, Lists and Macros

Rules

A rule is a YAML object, part of the rules file, whose definition contains at least the following fields:

- rule: shell_in_container
  desc: notice shell activity within a container
  condition: >
    evt.type = execve and 
    evt.dir = < and 
    container.id != host and 
    (proc.name = bash or
     proc.name = ksh)    
  output: >
    shell in a container
    (user=%user.name container_id=%container.id container_name=%container.name 
    shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)    
  priority: WARNING

Conditions

The key part of a rule is the condition field. A condition is a Boolean predicate expressed using the condition syntax. It is possible to express conditions on all supported events using their respective supported fields.

Here's an example of a condition that alerts whenever a bash shell is run inside a container:

container.id != host and proc.name = bash

The first clause checks that the event happened in a container (where container.id is equal to "host" if the event happened on a regular host). The second clause checks that the process name is bash.

If you want to be alerted only for each successful spawn of a shell in a container, add the appropriate event type and direction to the condition:

evt.type = execve and evt.dir = < and container.id != host and proc.name = bash

Therefore, a complete rule using the above condition might be:

- rule: shell_in_container
  desc: notice shell activity within a container
  condition: >
    evt.type = execve and 
    evt.dir = < and 
    container.id != host and 
    proc.name = bash    
  output: >
    shell in a container 
    (user=%user.name container_id=%container.id container_name=%container.name 
    shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)    
  priority: WARNING

Output

A rule output is a string that can use the same fields that conditions can use prepended by % to perform interpolation, akin to printf. For example:

Disallowed SSH Connection 
  (command=%proc.cmdline connection=%fd.name 
   user=%user.name user_loginuid=%user.loginuid container_id=%container.id 
   image=%container.image.repository)

could output:

Disallowed SSH Connection 
  (command=sshd connection=127.0.0.1:34705->10.0.0.120:22 
   user=root user_loginuid=-1 container_id=host 
   image=<NA>)

Outputs are usually written in a single line.
Modifying this output we try to present it to you in a more human-readable way.

Note that it's not necessary that all fields are set in the specific event. As you can see in the example above if the connection happens outside a container the field %container.image.repository would not be set and <NA> is displayed instead.

Outputs can also use the transform operators that are used in conditions. For example:

Disallowed SSH Connection 
  (command=%proc.cmdline connection=%fd.name 
   user=%toupper(user.name) user_loginuid=%user.loginuid container_id=%toupper(container.id)
   image=%container.image.repository)

could output:

Disallowed SSH Connection 
  (command=sshd connection=127.0.0.1:34705->10.0.0.120:22 
   user=ROOT user_loginuid=-1 container_id=HOST 
   image=<NA>)

To learn more about how Falco processes the output and related settings, see the output formatting page.

Priority

Every Falco rule has a priority which indicates how serious a violation of the rule is. This is similar to what we know as the severity of a syslog message. The priority is included in the message/JSON output/etc.

Here are the available priorities:

  • EMERGENCY
  • ALERT
  • CRITICAL
  • ERROR
  • WARNING
  • NOTICE
  • INFORMATIONAL
  • DEBUG

The general guidelines used to assign priorities to rules are the following:

  • If a rule is related to writing state (i.e. filesystem, etc.), its priority is ERROR.
  • If a rule is related to an unauthorized read of state (i.e. reading sensitive files, etc.), its priority is WARNING.
  • If a rule is related to unexpected behavior (spawning an unexpected shell in a container, opening an unexpected network connection, etc.), its priority is NOTICE.
  • If a rule is related to behaving against good practices (unexpected privileged containers, containers with sensitive mounts, running interactive commands as root), its priority is INFO.

Advanced Rule Syntax

A Falco rule can contain several of the following keys:

KeyRequiredDescriptionDefault
ruleyesA short, unique name for the rule.
conditionyesA filtering expression that is applied against events to check whether they match the rule.
descyesA longer description of what the rule detects.
outputyesSpecifies the message that should be output if a matching event occurs. See output.
priorityyesA case-insensitive representation of the severity of the event. Should be one of the following: emergency, alert, critical, error, warning, notice, informational, debug.
exceptionsnoA set of exceptions that cause the rule to not generate an alert.
enablednoIf set to false, a rule is neither loaded nor matched against any events.true
tagsnoA list of tags applied to the rule (more on this here).
warn_evttypesnoIf set to false, Falco suppresses warnings related to a rule not having an event type (more on this here).true
skip-if-unknown-filternoIf set to true, if a rule conditions contains a filtercheck, e.g. fd.some_new_field, that is not known to this version of Falco, Falco silently accepts the rule but does not execute it; if set to false, Falco reports an error and exits when finding an unknown filtercheck.false
sourcenoThe event source for which this rule should be evaluated. Typical values are syscall, k8s_audit, or the source advertised by a source plugin.syscall

Macros

Macros provide a way to define common sub-portions of rules in a reusable way.

By looking at the condition above it looks like both evt.type = execve and evt.dir = < and container.id != host would be used many by other rules, so to make our job easier we can easily define macros for both:

- macro: container
  condition: container.id != host
- macro: spawned_process
  condition: evt.type = execve and evt.dir = <

With these macros defined, we can then rewrite the above rule's condition as spawned_process and container and proc.name = bash.

- rule: shell_in_container
  desc: notice shell activity within a container
  condition: >
    spawned_process and 
    container and 
    proc.name = bash    
  output: >
    shell in a container
    (user=%user.name container_id=%container.id container_name=%container.name 
    shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)    
  priority: WARNING

For more examples of rules and macros, take a look the documentation on default macros and the rules/falco_rules.yaml file. In fact, both the macros above are part of the default list!

Lists

Lists are named collections of items that you can include in rules, macros, or even other lists.

Each list node has the following keys:

KeyDescription
listThe unique name for the list (as a slug)
itemsThe list of values

Here are some example lists as well as a macro that uses them:

- list: shell_binaries
  items: [bash, csh, ksh, sh, tcsh, zsh, dash]

- list: userexec_binaries
  items: [sudo, su]

- list: known_binaries
  items: [shell_binaries, userexec_binaries]

- macro: safe_procs
  condition: proc.name in (known_binaries)

Referring to a list inserts the list items in the macro, rule, or list. Therefore, our rule could become more general replacing proc.name = bash with proc.name in (shell_binaries), or even better, using the already included macro shell_procs:

- list: shell_binaries
  items: [bash, csh, ksh, sh, tcsh, zsh, dash]

- macro: shell_procs
  condition: proc.name in (shell_binaries)

- rule: shell_in_container
  desc: notice shell activity within a container
  condition: >
    spawned_process and 
    container and 
    shell_procs    
  output: >
    shell in a container
    (user=%user.name container_id=%container.id container_name=%container.name 
    shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)    
  priority: WARNING

Visibility

As mentioned above, lists can reference other lists, and macros can reference other macros. The only requirement is that to reference an object of the same kind (a list including another list, or a macro including another macro) they must have been defined previously.

However, if a macro included a list, this list might have been defined earlier or be defined at a later stage in the rules files. The same happens with a rule referencing a macro. This one doesn't need to be previously defined.

In other words, visibility is defined in cascade and is quite important:

  • A list can only reference lists defined before it.
  • A macro can only reference macros defined before it.
  • A macro can reference any list.
  • A rule can reference any macro.