Scanning an image and statically analysing Kubernetes manifests checks how an artefact is built before deployment, but neither looks at what a process does while it runs. Falco looks exactly there: it reads the Linux kernel system-call stream at runtime and flags when a process inside a container does something the rules describe as suspicious.

The project started at Sysdig in 2016, on top of the same kernel instrumentation the sysdig tool uses, and entered the CNCF Sandbox on 10 October 2018 — the first runtime security tool accepted into the Cloud Native Computing Foundation’s Sandbox. It is licensed Apache 2.0. The current version is 0.13.0, released on 12 November 2018.

Problem

Almost all container security controls are static: image vulnerability scanning, signature verification, admission policy, checking the privileges a manifest requests. They all answer “is this artefact acceptable?” before it starts. None answers “is this process, right now, doing something it should not?”.

At runtime the behaviours a scan cannot predict appear: a shell opened inside a production container, a read of /etc/shadow, a binary opening a connection to an unexpected host, an attempt to mount the host filesystem. They depend on execution, not on how the image is composed, and so escape any pre-deployment analysis.

Architecture

Falco observes system calls — the operations every process must hand to the kernel to read a file, open a socket, execute another program. Capture happens in the kernel through two alternative mechanisms that feed the same event stream:

  • a kernel module, the primary approach, which inserts the instrumentation directly into the kernel;
  • an eBPF probe, an alternative that runs the capture code inside the kernel’s eBPF sandbox without loading a module, at slightly higher overhead.

Events pass to userspace and are evaluated against a set of rules. A rule is written in YAML, with a condition expressed in the filtering language inherited from sysdig, close in syntax to that of tcpdump:

- rule: Terminal shell in container
  desc: A shell was used as the entrypoint/exec point into a container
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
  output: >
    A shell was spawned in a container (user=%user.name
    container_id=%container.id image=%container.image.repository)
  priority: NOTICE

When a condition matches an event, Falco emits an alert on the configured channel: stdout, syslog, a file, or an external program to which forwarding is delegated. The distribution ships a base rule set covering recurring scenarios — shell spawns in containers, sensitive-file access, writes to system directories, unexpected network activity, escape attempts — and you extend it with your own rules, reusable macro fragments and list values.

Critical point

The notable change in 0.13.0 is structural: Falco stops depending on a single event source. Up to the previous version the only stream was system calls; now each rule declares its own source through the source attribute, which defaults to syscall or is set to k8s_audit for the audit events of the Kubernetes API server.

To receive them, Falco exposes a civetweb-based webserver listening on a configurable port and accepting POST requests on a dedicated endpoint; the Kubernetes audit backend has to be configured to send the events there. The matching rules live in k8s_audit_rules.yaml and work on a domain distinct from syscalls: creation of privileged pods, overly permissive RBAC roles, secrets carrying credentials in clear text. The release’s example rule inspects the requestObject field of an audit event to spot a ConfigMap embedding AWS keys or passwords.

It follows that one engine observes two distinct planes with the same rule grammar: what processes do on the nodes, and what is requested of the control plane. A privileged pod starting and a shell opening inside that pod become events evaluated by the same tool, even though they arrive through independent paths.

Implications

Operationally Falco deploys in Kubernetes as a DaemonSet, one instance per node, so it observes the syscalls of every container running on that kernel. A second channel, fed by the API-server audit, covers requests to the control plane and is not per-node.

The detection model is descriptive and untrained: each rule states explicitly which combination of process, file, container and context counts as an event worth flagging. The output stays readable — the alert reports the condition that matched and the relevant fields — and a false positive becomes a rule problem, fixed by editing the condition, rather than an opaque behaviour to recalibrate. In exchange, coverage equals the rules that have been written: whatever no condition describes is not seen.

Limits

Falco observes and reports; it does not block. An alert says something happened, it does not prevent it; the follow-up action — killing a pod, isolating a node, opening a ticket — sits downstream and has to be built separately.

The fidelity of the stream depends on the kernel instrumentation: the kernel module and the eBPF probe must be built or fetched for the running kernel version, and an environment that prevents loading them leaves Falco without its primary source. The base rules are a generic starting point: applied unchanged to a real workload they make noise, because many actions the standard conditions flag as anomalous are legitimate for a specific application. And an adversary who knows the rules can choose paths no condition covers — detection is worth exactly as much as the rule that expresses it.


Cover image: Block diagram of the Linux kernel system call interface, showing the GNU C Library (glibc) in user space wrapping the kernel’s system… — diagram by Shmuel Csaba Otto Traian, CC BY-SA 3.0 — https://commons.wikimedia.org/wiki/File:Linux_kernel_System_Call_Interface_and_glibc.svg