CHAPTER 3
Container Runtime Protection

In previous chapters, we looked at the need to get the permissions correctly configured to protect other containers running on a host and indeed the host itself. In Chapter 6, “Container Image CVEs,” we will also look at protecting against common vulnerabilities and exploits (CVEs) to plug security holes in container images. The third major aspect of container security is at least as important from an operational perspective. That is the need to capture and potentially automatically remediate any issues when anomalous behavior is discovered from your running containers.

Only a handful of trustworthy and battle-worn container runtime security applications exist. Of those there is one Open Source tool that stands out from the crowd. Created by a company called Sysdig (sysdig.com) in 2016 and a member of the Cloud Native Computing Forum (CNCF), Falco (falco.org) excels at both container and host security rules enforcement and alerting. Of the more popular commercial tools there are Prisma Cloud Compute Edition (formerly Twistlock prior to acquisition) and Aqua from AquaSec.

Falco (sysdig.com/opensource/falco) offers exceptional Open Source functionality that can be used to create rulesets to force containers to behave in precisely the way you want. It also integrates with Kubernetes API Audit Events, which means that all sorts of orchestrator actions can be secured in addition. You can find more information here:

falco.org/docs/event-sources/kubernetes-audit.

In this chapter, we will look at installing Falco and then explore its features and how it can help secure our container runtime and underlying hosts, in the same way that some commercial products do, but without any associated fees. We will also explore using some of its rulesets and how to make changes to them yourself.

Running Falco

Following true Cloud Native methodology, we will use a container image to spawn Falco. That said, there are Linux rpm, deb, and binary files that you can install or execute directly, too, which appears to be the preferred route for their installation.

You can run Falco either on a host or by a userland container that additionally needs to access a pre-installed driver on the underlying host. Falco works by tapping into the kernel with elevated permissions to pick up the kernel's system calls (syscalls), and the driver is needed to offer that required functionality. We also need to provide Falco with the requisite permissions to enable such functionality. As described in Chapter 1, “What Is A Container?,” for a container runtime we define these permissions using kernel capabilities. To get an idea of what is available, you could do worse than looking over some of the names of the kernel capabilities in the manual (using the command man capabilities). Various versions of the manual are online too, such as this:

man7.org/linux/man-pages/man7/capabilities.7.html

To protect the underlying host, we will run Falco with as few privileges as possible. Be warned, however, that you will need a kernel version of v5.8 or higher to make use of the extended Berkeley Packet Filter (eBPF) driver without running a one-off --privileged container to install that driver to the underlying host(s) that Falco will run on. The Berkeley Packet Filter has been extended to allow increased access to the networking stack to applications via the kernel.

If you are lucky enough to have a kernel of v5.8 or later, the way around the one-off driver installation is to add the CAP_SYS_BPF option to your running container at startup time, which the more modern kernels will support. Add it using this command-line switch:

--cap--add SYS_BPF

For this demonstration, we will not assume that you have that kernel version, so we will install the driver on a host where we will use the one-off container method. The commands are as follows:

$ docker pull falcosecurity/falco-driver-loader:latest
$ docker run --rm -it --privileged -v /root/.falco:/root/.falco 
-v /proc:/host/proc:ro -v /boot:/host/boot:ro  

-v /lib/modules:/host/lib/modules:ro 
-v /usr:/host/usr:ro -v /etc:/host/etc:ro 
falcosecurity/falco-driver-loader:latest

As you can see, we are using the insecure --privileged switch to gain the elevated permissions required to install the Falco driver. Listing 3.1 shows part of the output from the command, in which Dynamic Kernel Module Support (DKMS) is called into action on Debian derivatives and a kernel module is used.

Although the kernel version (4.15.0.20-generic) seems like a long way off from version 5.8, around version v4.19 the versions jumped to v5.4. To check that the process has automatically loaded up the kernel module as hoped, we can run this lsmod command:

$ lsmod | grep falco
falco                 634880  0

As we can see, falco is present in the list of loaded modules, so we can continue to proceed. Obviously, if you installed packages directly onto the host as the root user, this step would not be needed, but it is important to illustrate that container protection security tools also have trade-offs, and suffice to say functionality like rootless mode will not accommodate such functionality without some heartache. Relinquishing an undefined security control, such as having a common attack vector across all hosts, to onboard a security tool to protect running containers is a necessary evil; in this case, the kernel module is essential to Falco's functionality. Be aware that you are allowing the tool to tap into the very lowest level of a host's innards (and its running containers), so you need to be completely sure that the security product to which you are offering privileged access is fully trustworthy. On a large, containerized estate, with orchestrators and potentially tens of thousands of running containers on differing varieties of hosts, the fact that you are adding another attack vector to each and every host in the estate needs to be carefully considered. You are effectively opening up a predictable security hole (that is, it is predictable if an attacker knows that a privileged container runs on each host) that can be exploited throughout the estate if a vulnerability is found.

Next, to run our Falco container, we will run the following long command all on one line ideally to enable the kernel capability CAP_SYS_PTRACE. According to the SYS_PTRACE man page (man7.org/linux/man-pages/man2/ptrace.2.html), we can control and manipulate other processes with this privilege as well as move data into the memory space of processes.

$ docker run --rm -it --security-opt apparmor:unconfined  
--cap-add SYS_PTRACE  
--pid=host $(ls /dev/falco* | xargs -I {} echo --device {}) -v
/var/run/docker.sock:/var/run/docker.sock
falcosecurity/falco-no-driver:latest

Note that we're demonstrating Falco on a Linux Mint machine (which is based on Ubuntu 18.04), and this command uses AppArmor effectively to stop rogue processes accessing several locked-away parts of a system. To use it, we also need to add the following switch to provide the required permissions to our container:

--security-opt apparmor:unconfined

As demonstrated in Chapter 1, you might also recognize that the container is offered the ability to access the host's process table namespace with the --pid switch on the Docker command.

Think about this for a moment. From a security vendor's perspective, AppArmor has clearly made an effort to reduce the attack surface its product brings to each host. However, from an organization's point of view, there's definitely a significant trade-off. We are effectively switching off all the protection afforded by AppArmor for this container and offering the tool the ability to poison or break other processes. That applies not just to our container runtime but our host(s) as a whole. Do not be mistaken; Falco is certainly not alone when it comes to this elevated permissions requirement for runtime protection.

After we have run the previous command, its brief output includes information as follows:

2020-08-09T12:27:54+0000: Falco initialized with configuration file /etc/falco/falco.yaml
2020-08-09T12:27:54+0000: Loading rules from file
/etc/falco/falco_rules.yaml:
2020-08-09T12:27:54+0000: Loading rules from file
/etc/falco/falco_rules.local.yaml:
2020-08-09T12:27:54+0000: Loading rules from file
/etc/falco/k8s_audit_rules.yaml:

Thanks to the fact that we entered the command as shown earlier, without adding -d to daemonize the container and detach the terminal from it, the STDOUT output (direct to the terminal) immediately starts listing some useful insights into what's happening on the host machine. Let's see what we can expect from Falco by looking at some of the output now. The first example is related to filesystem access:

2020-08-09T13:35:47.930163243+0000: Warning Sensitive file opened for 
reading by non-trusted program (user=<NA> program=pkexec 
command=pkexec 
/usr/lib/x86_64-linux-gnu/cinnamon-settings-daemon/csd-backlight-helper
--set-brightness 828 -b firmware -b platform 
-b raw file=/etc/pam.d/common-account parent=csd-power 
gparent=cinnamon-sessio ggparent=lightdm gggparent=lightdm 
container_id=host image=<NA>)

We can see that “Sensitive file opened for reading by non-trusted program” has flagged an issue. Let's try to spawn a container from an image:

2020-08-09T13:45:46.935191270+0000: Notice A shell was spawned in a 
container with an attached terminal (user=root <NA> (id=8f31495aeedf)
shell=bash parent=<NA> cmdline=bash terminal=34816
container_id=8f31495aeedf image=<NA>)

As we can see, Bash was used to access a running container. The flagged issue is listed as “A shell was spawned in a container with an attached terminal.”

Another flagged issue, this time more specific to the host, is as shown here:

2020-08-09T13:48:37.040867784+0000: Error File below / or /root opened 
for writing (user=root command=bash parent=sudo 
file=/root/.bash_history-18236.tmp program=bash container_id=host
image=<NA>)
2020-08-09T13:48:37.041053025+0000: Warning Shell history had been 
deleted or renamed (user=root type=rename command=bash 
fd.name=<NA> name=<NA> path=<NA>
oldpath=/root/.bash_history-18236.tmp host (id=host))

We can see that in the /root directory a process has written to a temporary file while the .bash_history file, used to record typed Bash commands, was probably opened/closed and appended to.

Another example alert might be this container warning:

2020-08-09T15:41:28.324617000+0000: Notice Container with sensitive 
mount started (user=root command=container:3369c68859c6 
dangly_goldwasser (id=3369c68859c6)
image=falcosecurity/falco-no-driver:latest mounts=/var/run/docker.sock:/var/run/docker.sock::true:rprivate)

We can see that a volume has been mounted by none other than Falco itself so that it can mount the Docker socket to tap into Docker Engine.

Configuring Rules

Next, we will look at how Falco's rulesets are constructed. Here is a more desktop-oriented rule, which should prevent applications (other than Skype or WebEx) from accessing the local camera:

- rule: access_camera
  desc: a process other than skype/webex tries to access the camera
  condition: evt.type = open and fd.name = /dev/video0 and not proc.name
  in (skype, webex)
  output: Unexpected process opening camera video device
 (command=%proc.cmdline)
  priority: WARNING

As we can see, the rule consists of a name and description followed by three criteria. They are the condition Falco should look out for, the output it should report, and the priority level of the output.

Here is a container-specific rule to examine a bit closer:

- rule: change_thread_namespace
  desc: an attempt to change a program/thread's namespace
  (commonly done as a part of creating a container) by calling setns.
  condition: syscall.type = setns and not proc.name in
  (docker, sysdig, dragent)
  output: "Namespace change (setns) by unexpected program
  (user=%user.name command=%proc.cmdline container=%container.id)"
  priority: WARNING

This rule pays close attention to a container moving between namespaces. The setns syscall that is marked as important is used to change namespace. The rule, however, ignores the event if docker, sysdig, or dragent initiate it.

Another example is a case study that Sysdig wrote about to help explain how a CVE could be mitigated using Falco, at the end of 2019. It was CVE-2019-14287 (cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-14287) that allowed a simple command to be run to make the sudo command run commands as the root user. To exploit the CVE, it was apparently as simple as using the sudo command as follows:

$ sudo -u#-1

In Listing 3.2 we can see the rule that the Sysdig team concocted to detect and then block the exploit within the CVE.

Changing Rules

If you run the Docker command to check the help output, you are offered a number of useful choices to remember, as shown here:

$ docker run -it falcosecurity/falco-no-driver:latest falco --help

And you can list all the rules currently loaded up into Falco with this syntax:

$ docker run -it falcosecurity/falco-no-driver:latest falco -L

The output includes the rule name along with a description such as this example:

Rule                                Description
----                                -----------
Create Symlink Over Sensitive Files Detect symlink over sensitive files

Although there are certainly more graceful ways of editing rules and configuration for Falco (which we will look at in a moment), if you use the container approach to run Falco, it is possible to extract the rules file that you want to change and save it to the local machine. Then you can simply mount the local machine volume when Falco fires up, and your changing configuration and rules will be loaded up.

You can initially copy the files out of the container with this command, where a docker ps command has given you the hash ID of your container previously:

$ docker cp 1f7591607c7d:/etc/falco/falco_rules.yaml .

Simply repeat the previous command for these files:

falco_rules.local.yaml, falco.yaml,  k8s_audit_rules.yaml

As you might imagine, you should place your own custom rules within the falco_rules.local.yaml file, which, barring comments, is mostly empty and not overwritten with version upgrades.

To load up changes, mount your volume as so with the additional -v $(pwd):/etc/falco/ option that mounts the /etc/falco directory from inside the container to your current working directory on your local machine:

$ docker run --rm -it --security-opt apparmor:unconfined 
--cap-add SYS_PTRACE 
--pid=host $(ls /dev/falco* | xargs -I {} echo --device {}) -v
$(pwd):/etc/falco/  
-v /var/run/docker.sock:/var/run/docker.sock  
falcosecurity/falco-no-driver:latest

The bundled rules are impressive and well worth a look. Listing 3.3 shows a Kubernetes example.

It's not 100% clear, but the commercial, enterprise method used to update rules appears to be connecting to a back end. Rather than extracting the configuration and rules files from a running container, Falco also offers the ability to install rules in a different way. On Docker Hub (hub.docker.com/r/sysdig/falco_rules_installer), there is a container image created by Sysdig that will allow you to update rules via a running container. Its purpose is to first validate existing rules and then inspect any custom rules and deploy them to a suitable back end. The command would look like this, for example:

$ docker run --rm --name falco-rules-installer -it 
-e DEPLOY_HOSTNAME=https://my-sysdig-backend.com 
-e [email protected] 
-e DEPLOY_USER_PASSWORD=<my password>  
-e VALIDATE_RULES=yes -e DEPLOY_RULES=yes  
-e CREATE_NEW_POLICIES=no  
-e SDC_SSL_VERIFY=True sysdig/falco_rules_installer:latest

For our purposes, though, copying rules out of a running container makes sense. For the host-installed version, you can also pass the -c switch to point the daemon at a different configuration file. It is also quite possible to point, multiple times, at directories where your rules reside with the -r switch, too.

Macros

Falco also employs the concept of using macros. The example that their documentation offers for a simple macro is as follows:

- macro: in_container
  condition: container.id != host and proc.name = sh

This example could be reused across multiple rules without having to explicitly rewrite it each time and offer significant time-savings.

Lists

It is also possible to use lists so that collections of items can be grouped together more easily to make them more repeatable. The following is one example:

- list: common_binaries
  items: [netcat, iftop, ngrep]

Here, we can avoid explicitly writing all the binaries for Linux shells and instead just refer to a list of shell_binaries.

Getting Your Priorities Right

The following are categories for rule priorities:

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

These categories will allow you to sort alerts into a more meaningful set of results and allow the ability to react accordingly. As we saw in the other rules, within your rules, you would add a line such as this within the following example pseudocode stanza:

- rule: A custom rule
  desc: Rule description
  condition: container.privileged=true
  priority: WARNING

Tagging Rulesets

You can also group rules and alerts with tags to help with identifying issues more clearly. The tagging also offers the ability to explicitly run only certain rules with the relevant tags, for example. The previous example is shown expanded next to include tags. The -T switch disables rules with a certain tag, and the lowercase -t switch means that you will only run those rules with the tags listed after that switch.

- rule: A custom rule
  desc: Rule description
  condition: container.privileged=true
  priority: WARNING
  tags: [database, cis]

Outputting Alerts

In addition to the standard Unix-like syslog log forwarding, there are other ways to receive alerts from Falco. To use syslog, you can simply tweak your configuration this way:

syslog_output:
  enabled: true

But to use ChatOps alerts, via Slack, for example, Sysdig has created a repository in GitHub (github.com/falcosecurity/falcosidekick) to assist with just that.

The documentation describes the service that the code will provide as “a simple daemon for enhancing available outputs for Falco.” The list of compatible recipients is lengthy and includes most of the usual suspects, such as Slack, Datadog, AWS Lambda, Opsgenie, Rocketchat, and SMTP for email.

A nice touch is that you can even spawn it as another container; to do that, you would use syntax such as this:

$ docker run -d -p 2801:2801 -e SLACK_WEBHOOKURL=XXXXX  
falcosecurity/falcosidekick

Additionally, the documentation provides lots of pointers on how to tweak each of the webhook outputs to your needs and enjoy real-time messaging as you want in order to set off emergency pagers at 4 a.m. or just report innocuous events to a chat channel.

Summary

There is no doubt that Falco offers extensive security functionality for both container runtime and hosts. It also plays nicely with Kubernetes, and as of version v0.13.0 API Audit Events can be captured as an event source so that Falco can be rolled out across a cluster to offer genuine insight into what containers and hosts are getting up to in the quiet hours. Supported Kubernetes actions from Falco include the creation and deletion of resources (pods, deployments, daemon sets, and so on), changes to ConfigMaps and secrets, volume mounts, host networking, granting cluster-admin access, and using ConfigMaps for overly sensitive information.

Even the Open Source version of Falco is an impressive, battle-hardened piece of software. And, its commercial products have a notable enterprise client list using the paid-for Enterprise Falco and Sysdig Secure products. If you are to trust your cloud estate with security tools running with elevated permissions, then clearly it makes sense to use the most reputable tool that you can find on the market to avoid bouts of insomnia.

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

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