Chapter 2. Getting Started with Falco on Your Local Machine

Now that you’re acquainted with the possibilities that Falco offers, what could be the best way to familiarize yourself with Falco if not to try it?

In this chapter, you will discover how easy it is to install and run Falco on a local machine by following some simple instructions. We’ll walk you through it step-by-step, analyzing and familiarizing you with its core concepts and functions. We will generate an event that Falco will detect for us by simulating a malicious action and learning how to read Falco’s notification output. We’ll finish the chapter by giving you some manageable approaches to customizing your installation.

Running Falco on Your Local Machine

Although Falco is not a typical application, installing and running it on a local machine is quite simple. All you need is a Linux host or a virtual machine and a terminal. Then you have to install two components: the userspace program (named falco) and a driver. The driver is needed to collect system calls, which are one possible data source for Falco. For simplicity, we will focus only on system call capture in this chapter. (We will learn more about the available drivers and why we need them to instrument the system in Chapter 3 and explore other alternative data sources in Chapter 4.) For the moment, you only need to know that the default driver, which is implemented as a Linux kernel module, is enough to collect system calls and start using Falco.

Several methods are available to install these components, as you will see in Chapter 8. However, in this chapter, we’ve opted for the binary package. It works with almost any Linux distribution and has no automation: you can touch its components with your hands. The binary package includes the falco program, the falco-driver-loader script (a utility to help us install the driver), and many other required files. You can download this package from the official website of The Falco Project (https://falco.org/), along with additional, comprehensive information about installing it. So, let’s do it!

Downloading and Installing the Binary Package

The Falco’s binary package is distributed as a single tarball compressed with GNU zip (gzip).

The tarball file is named falco-x.y.z-arch.tar.gz, where x.y.z is the version of a Falco release and arch is the intended architecture (e.g., x86_64) for the package.1

All packages are listed at Falco’s Getting Started page. You can grab the URL of the binary package and download it locally, for example, using curl:

curl -L -O https://download.falco.org/packages/bin/x86_64/falco-0.30.0-x86_64.tar.gz

After downloading the tarball, uncompressing and untarring it is quite simple:

tar -xvf falco-0.30.0-x86_64.tar.gz

The tarball content, which we have just extracted, is intended to be copied directly to the local filesystem’s root (i.e., /), without any special installation procedure. To copy it, run, as root:

sudo cp -R falco-0.30.0-x86_64/* /

The last thing we need to install is the driver.

Installing the Driver

System calls are Falco’s default data source. In order to instrument the Linux kernel and collect these system calls, it needs a driver: either a Linux Kernel Module or an eBPF probe. The driver needs to be built for the specific version and configuration of the kernel on which Falco will run. Fortunately, The Falco Project provides literally thousands of pre-built drivers for the vast majority of the most common Linux distributions, with various kernel versions available for download. If a pre-built driver for your distribution and kernel version is not yet available, the files we installed in the previous section include the source code of both the Kernel Module and the eBPF, so it is also possible to build the driver locally.

This might sound like a lot, but, the falco-driver-loader script you’ve just installed can do all these steps. All you need to do before using the script is install a few necessary dependencies:

  • Dynamic Kernel Module Support (DKMS)

  • GNU make

  • The Linux kernel headers

Depending on the package manager you’re using, the actual package names can vary; however, they aren’t difficult to find.

Once you have installed the above packages, you are ready to run the falco-driver-loader script as a root user. If everything goes well, the script output should look something like this:

$ sudo falco-driver-loader
* Running falco-driver-loader for: falco version=0.30.0, driver version=3aa7a83bf7b9e6229a3824e3fd1f4452d1e95cb4
* Running falco-driver-loader with: driver=module, compile=yes, download=yes
* Unloading falco module, if present
* Trying to load a system falco module, if present
* Looking for a falco module locally (kernel 5.14.9-arch2-1)
* Trying to download a prebuilt falco module from https://download.falco.org/driver/3aa7a83bf7b9e6229a3824e3fd1f4452d1e95cb4/falco_arch_5.14.9-arch2-1_1.ko
curl: (22) The requested URL returned error: 404
Unable to find a prebuilt falco module
* Trying to dkms install falco module with GCC /usr/bin/gcc

From the above output, we can catch some useful information. The first line reports the versions of Falco and the driver that are being installed. The subsequent line tells us that the script will try to download a pre-built driver so it can install a Kernel Module. If the pre-built driver is not available, Falco will try to build it locally. The rest of the output shows the process of building and installing the module via DKMS, and finally, that the module has been installed and loaded.

Starting Falco

To start Falco, we just have to run it as a root user.2

$ sudo falco
Mon Oct 11 10:58:51 2021: Falco version 0.30.0 (driver version 3aa7a83bf7b9e6229a3824e3fd1f4452d1e95cb4)
Mon Oct 11 10:58:51 2021: Falco initialized with configuration file /etc/falco/falco.yaml
Mon Oct 11 10:58:51 2021: Loading rules from file /etc/falco/falco_rules.yaml:
Mon Oct 11 10:58:51 2021: Loading rules from file /etc/falco/falco_rules.local.yaml:
Mon Oct 11 10:58:51 2021: Loading rules from file /etc/falco/k8s_audit_rules.yaml:
Mon Oct 11 10:58:52 2021: Starting internal webserver, listening on port 8765

Note the configuration and the rules files’ paths. We’ll look at these in more detail in chapters 9 and 13. Finally, in the last line, we can see that a web server has been started. Why? Falco supports various data sources, including the Kubernetes Audit Logs (see chapter 4), which delivers events through a webhook.

Once Falco prints this startup information, it is ready to issue a notification whenever a condition in the loaded ruleset is met. Right now, you probably won’t see any notifications (if nothing malicious is running on your system). In the next section, we will generate a suspicious event.

Finally, keep in mind that in this chapter, to get you used to it, we have simply run Falco as an interactive shell process; a simple Ctrl + C is enough to end the process. Throughout the book, we will show you different and more sophisticated ways to install and run it.

Generating Events

There are millions of ways to generate events. In the case of system calls, in reality, many events happen continuously as soon as processes are running. However, to see Falco in action, we must focus on events that can trigger an alert. As you’ll recall, Falco comes pre-loaded with an out-of-the-box set of rules that cover the most common security scenarios. In short, we must simulate a malicious action within our system!

To express unwanted behaviors, Falco uses rules. Therefore we have to pick a rule as our target. In the course of the book and particularly in Chapter 13, we will discover together the complete anatomy of a rule, how to interpret and write a condition using Falco’s rule syntax, and which fields are supported in the conditions and outputs. For the moment, let us briefly recall what a rule is and explain its structure by considering a real example.

- rule: Write below binary dir
  desc: an attempt to write to any file below a set of binary directories
  condition: >
    bin_dir and evt.dir = < and open_write
  output: >
    File below a known binary directory opened for writing (user=%user.name user_loginuid=%user.loginuid
    command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository)
  priority: ERROR

A rule declaration is a YAML object with several keys. The first key, rule, uniquely identifies the rule within a ruleset (one or more YAML files containing rule definitions). The second key, desc, allows the rule’s author to briefly describe what the rule will detect.

The condition key, arguably the most important one, allows expressing a security assertion using some straightforward syntax. Various boolean and comparison operators can be combined with fields (which hold the collected data) to filter only relevant events. Supported fields and filters are covered in more detail in Chapter 6.

As long as the condition is false, nothing will happen. The assertion is met when the condition is true, and then an alert will be fired immediately. The alert will contain an informative message, as defined by the rule’s author using the output key of the rule. The value of the priority key will be reported too. The content of an alert is covered in more detail in the next section.

The condition’s syntax can also make use of a few more constructs, like list and macro, that can be defined in the ruleset alongside rules. As the name suggests, a list is a list of items that can be reused across different rules. Similarly, macros use pieces of conditions. For completeness, here are the two macros (bin_dir and open_write) utilized in the condition key of the Write below binary dir rule:

- macro: bin_dir
  condition: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
- macro: open_write
  condition: (evt.type=open or evt.type=openat) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0

At runtime, when rules are loaded, macros expand. Consequently, we can imagine the final rule condition will be similar to:

(evt.type=open or evt.type=openat) and evt.is_open_write=true and fd.typechar='f' and fd.num>=0 
and 
evt.dir = < 
and 
fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)

Notably, the conditions make extensive use of fields. In the above example, you can easily recognize which parts of the condition are fields (such as evt.type, evt.is_open_write, fd.typechar, evt.dir, and fd.directory) since they are followed by a comparison operator (e.g., =, in). Fields usually contain a dot (.) in the middle, because fields with a similar context are grouped together in classes. The part before the dot represents the class (for example, evt and fd are classes).

Although you might not thoroughly understand the condition’s syntax yet, you don’t need to at the moment. All you need to know is that creating a file (which implies opening a file for writing) under one of the directories listed within the condition (like /bin) should be enough to trigger the rule’s condition. Let’s try it.

First, start Falco with our target rule loaded. Note that the Write below binary dir rule is included in /etc/falco/falco_rules.yaml, which is loaded by default when starting Falco. Luckily, you do not need to copy it manually. Just open a terminal and run:

sudo falco

Second, trigger the rule by creating a file below the /bin directory. A straightforward way to do this is by opening another terminal and typing:

sudo touch /bin/surprise

Now, if you return to the first terminal with Falco running, you should find a line in the log (that is, an alter emitted by Falco) that looks like the following:

16:52:09.350818073: Error File below a known binary directory opened for writing (user=root user_loginuid=1000 command=touch /bin/surprise
 file=/bin/surprise parent=sudo pcmdline=sudo touch /bin/surprise gparent=zsh container_id=host image=<NA>)

Falco caught us! Fortunately, that’s exactly what we wanted to happen. (We’ll look at this output in more detail in the next section.)

Rules let us tell Falco which security policies we want to observe (expressed by the condition key) and which information we wish to receive (as specified by the output key) if a policy has been violated. Eventually, Falco emits an alert (outputs a line of text) whenever an event meets the condition defined by a rule. If you run the same command again, a new alert will fire.

After trying out the example we have just given you, why not try the other rules by yourself? To facilitate this, the Falcosecurity organization offers a tool to generate events for just this reason. The event-generator is a simple command-line tool. It does not require special installation steps. You can download the latest release and uncompress it wherever you prefer. It comes with a collection of events that match many of the rules included in the default Falco ruleset. For example, to generate an event that meets the condition expressed by the Change Thread Namespace (another Falco rule), you can type the following in a terminal window:

$ ./event-generator run syscall.ChangeThreadNamespace
Warning

Be aware that this tool might alter your system. For example, since the tool’s purpose is to reproduce real malicious behavior, some actions modify files and directories below, such as /bin, /etc, and /dev. Make sure you fully understand the purpose of this tool and its options before using it.

Interpreting the Falco Output

Let’s take a closer look at the alert notification in the output. What important information does it contain?

16:52:09.350818073: Error File below a known binary directory opened for writing (user=root user_loginuid=1000 command=touch /bin/surprise
 file=/bin/surprise parent=sudo pcmdline=sudo touch /bin/surprise gparent=zsh container_id=host image=<NA>)

This apparently complex line is actually composed of only three main elements separated by whitespace: timestamp, severity, and message. Let’s take a look at each of these.

Timestamp

Intuitively, the first element is the timestamp (followed by a colon: 16:52:09.350818073:). That’s when the event was generated. By default, it is displayed in the local time zone and includes nanoseconds. You can, if you like, configure Falco to display times in ISO 8601 format, including date, nanoseconds, and timezone offset (in UTC).

Severity

The second element indicates the severity (followed by a whitespace, e.g., Error) of the alert, as specified by the priority key in the rule. It can assume one of the following values (ordered from the most to the least severe): Emergency, Alert, Critical, Error, Warning, Notice, Informational, Debug. Moreover, it is possible to specify the minimum rule priority level we want to get in the output. So Falco allows us to filter out these alerts that are not important for us and thus reduce the noisiness. The configuration file /etc/falco/falco.yaml allows the changing of this parameter, named priority, and defaulted to Debug (therefore, all priority levels are included by default). If, for example, we changed the configuration to Notice as the minimum level, then the rules with priority equal to Informational or Debug will be discarded.

Message

The last and the most essential element is the message. It is a string produced according to the format specified by the output key. Its peculiarity lies in using placeholders, which the Falco engine replaces with the event data, as we will see in a moment.

Normally, the output key of a rule begins with a descriptive sentence to facilitate identifying the type of problem (e.g., File below a known binary directory opened for writing). Then it includes some placeholders (e.g., %user.name), which will be populated with actual values (e.g., root) when outputted. You can easily recognize placeholders since they start with a % symbol followed by one of the event’s supported fields. These fields can be used in the condition key of a Falco rule and in the output key as well.

The beauty of this feature is that you can have a different output format for each security policy. This immediately gives you the most relevant information of that violation accident without having to navigate hundreds of fields.

Although this textual notification would seem to include all information and is already sufficient to be consumed by many other programs, there are other options, too. Indeed, plain text is not the only outputting format supported. If you want or if you need, Falco is also able to output notifications in JSON format by simply changing a configuration parameter. We will look at the configuration in the next section.

Using the JSON output format has the advantage of being easily parsable by consumers. When enabled, Falco will emit in output a JSON line for each alert that will look like the following, which we pretty-printed to improve readability:

{
  "output": "11:55:33.844042146: Error File below a known binary directory opened for writing (user=root user_loginuid=1000 command=touch /bin/surprise file=/bin/surprise parent=sudo pcmdline=sudo touch /bin/surprise gparent=zsh container_id=host image=<NA>)",
  "priority": "Error",
  "rule": "Write below binary dir",
  "time": "2021-09-13T09:55:33.844042146Z",
  "output_fields": {
    "container.id": "host",
    "container.image.repository": null,
    "evt.time": 1631526933844042146,
    "fd.name": "/bin/surprise",
    "proc.aname[2]": "zsh",
    "proc.cmdline": "touch /bin/surprise",
    "proc.pcmdline": "sudo touch /bin/surprise",
    "proc.pname": "sudo",
    "user.loginuid": 1000,
    "user.name": "root"
  }
}

This output format reports the same text message as before. Additionally, each piece of information is separated into distinct JSON properties. You may also have noticed some extra data: for example, the rule identifier is present this time ("rule": "Write below binary dir").

To try it right now, when starting Falco, simply pass a flag via the command-line argument to override the default configuration:

sudo falco -o json_output=true

Alternatively, you can edit /etc/falco/falco.yaml and set json_output to true. This will enable the JSON format every time Falco starts, without the flag.

Customizing Your Falco Instance

When you start Falco, it loads several files. In particular, it first loads the main (and only) configuration file, as the startup log shows:

Thu Sep  9 14:16:03 2021: Falco initialized with configuration file /etc/falco/falco.yaml

Falco looks for its configuration file at /etc/falco/falco.yaml, by default. That’s also where the provided configuration file is installed. If desired, you can specify another configuration file path using the -c command-line argument when running Falco. Whatever file location you prefer, the configuration must be a YAML file mainly containing a collection of key-value pairs. Let’s see some available configuration options.

Rules Files

One of the most essential options, and the first you find in the provided configuration file, is the list of the rule files to be loaded:

rules_file:
  - /etc/falco/falco_rules.yaml
  - /etc/falco/falco_rules.local.yaml
  - /etc/falco/k8s_audit_rules.yaml
  - /etc/falco/rules.d

Despite the naming (for backward compatibility), rules_file can be either a list of rule files or a list of directories containing rule files. So, if an entry is a file, Falco reads directly. In the case of a directory, Falco will read every file in that directory.

The order matters here. The files are loaded in the presented order (files within a directory are loaded in alphabetical order). Users can customize predefined rules by simply overriding them in files that appear later in the list. For example, let’s assume that you wanted to change the severity of the Write below binary dir rule (which is included in /etc/falco/falco_rules.yaml) from Error to Emergency. All you need to do is edit /etc/falco/falco_rules.local.yaml (which appears later and is intended to add local overrides) and write:

- rule: Write below binary dir
  priority: EMERGENCY
  append: true

Output Channels

There is a group of options that control Falco’s available output channels, allowing you to specify where the security notifications should go. Furthermore, you can enable more than one simultaneously. You can easily recognize them within the configuration file (recall, you can find it in /etc/falco/falco.yaml) since their keys are suffixed with _output.

By default, the only two enabled output channels are stdout_output, which instructs Falco to send alert messages to the standard output, and syslog_output, which sends them to the system logging daemon. Their configurations are:

stdout_output:
  enabled: true
syslog_output:
  enabled: true

Falco provides several other advanced built-in output channels. For example:

file_output:
  enabled: false
  keep_alive: false
  filename: ./events.txt

When file_output is enabled, Falco will also write its alerts to the file specified by the subkey filename.

Other output channels allow us to consume alerts in sophisticated ways and integrate with third parties. For instance, if you want to pass the Falco output to a local program, you can use:

program_output:
  enabled: false
  keep_alive: false
  program: "jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/XXX"

Once you enable this, Falco will execute the program for each alert and write its content to the program’s standard output. You can set any valid shell command to the program subkey—so this is an excellent opportunity to show off your favorite one-liners.

If you simply need to integrate with a webhook, a more convenient option is to use the http_output:

http_output:
  enabled: false
  url: http://some.url

A simple HTTP POST request will be sent to url for each alert. That makes it really easy to connect Falco to other tools, like Falcosidekick, which will forward alerts to Slack, Teams, Discord, Elasticsearch, and many other destinations.

Last but not least, Falco comes with a gRPC API and a corresponding output (such as grpc_output). For instance, enabling gRPC API and the gRPC output channel allows you to connect Falco to falco-exporter, which, in turn, will export metrics to Prometheus.

Falcosidekick and falco-exporter are open source projects you can find under the Falcosecurity GitHub organization. In Chapter 12, we will meet these tools again and learn how to work with outputs.

Conclusion

This chapter taught you how to install and run Falco in your local machine as a playground. You saw some simple ways to generate events and decode the output. We then looked at how to use the configuration file to customize Falco’s behavior. Loading and extending rules are the primary way to instruct Falco on what to protect. Likewise, configuring the output channels empowers us to consume notifications in ways that meet our needs.

Armed with this knowledge, you can start experimenting with Falco confidently. The rest of this book will deepen what you’ve learned here and eventually help you master Falco completely.

1 At the time of writing, the only officially supported architecture is x86_64. The full package list is available at https://falco.org/docs/getting-started/download/.

2 Falco needs to run with root privilege to operate the driver that in turn collects syscalls. However, alternative approaches are possible. For example, you can learn from Falco’s Running page how to run Falco in a container with the principle of least privilege.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset