CKS — Falco Rules

9 minute read

Falco Rules

Falco’s power comes entirely from its rules. A rule defines what to detect (condition), how to describe it (output), and how severe it is (priority). On the CKS exam, you will need to read, write, and modify Falco rules. This is non-negotiable — expect at least one question that requires creating or editing a custom rule.

Rule Files and Load Order

Falco loads rule files in the order specified in /etc/falco/falco.yaml:

rules_file:
  - /etc/falco/falco_rules.yaml          # Default rules — DO NOT EDIT
  - /etc/falco/falco_rules.local.yaml    # Custom rules and overrides

Load order matters. Rules in later files override rules with the same name in earlier files. This is how you customize Falco without touching the default rule file.

File Purpose Edit?
/etc/falco/falco_rules.yaml 300+ default rules from Falco project Never
/etc/falco/falco_rules.local.yaml Your custom rules and overrides Yes

Rule Syntax

Every Falco rule has this structure:

- rule: <name>
  desc: <human-readable description>
  condition: <Sysdig filter expression — when this rule fires>
  output: <format string — what the alert says>
  priority: <severity level>
  tags: [<tag1>, <tag2>]
  enabled: true

Fields explained:

Field Required Description
rule Yes Unique name for this rule
desc Yes Description of what the rule detects
condition Yes Boolean filter expression using Falco’s field syntax
output Yes Alert message template with field placeholders
priority Yes EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG
tags No Categorization tags (e.g., container, network, filesystem)
enabled No Set to false to disable (default: true)

Priority Levels

EMERGENCY  →  System is unusable
ALERT      →  Action must be taken immediately
CRITICAL   →  Critical conditions
ERROR      →  Error conditions
WARNING    →  Warning conditions (most common for CKS)
NOTICE     →  Normal but significant conditions
INFORMATIONAL → Informational messages
DEBUG      →  Debug-level messages

Condition Field — Falco Filter Syntax

Conditions use Sysdig filter syntax. These are the building blocks:

System call classes:

Filter Meaning
spawned_process A new process was created (execve)
open_read A file was opened for reading
open_write A file was opened for writing
container Event happened inside a container
evt.type = execve Specific syscall type
evt.type in (open, openat) Syscall is one of these types

Field references:

Field Meaning
proc.name Process name
proc.pname Parent process name
proc.cmdline Full command line
fd.name File descriptor name (file path)
container.id Container ID
container.name Container name
container.image.repository Container image name
k8s.ns.name Kubernetes namespace
k8s.pod.name Kubernetes pod name
user.name User who triggered the event
evt.type Syscall type

Operators:

Operator Example
= proc.name = bash
!= container.id != host
in proc.name in (bash, sh, zsh)
contains fd.name contains /etc
startswith fd.name startswith /etc/
pmatch fd.name pmatch (/etc/shadow, /etc/passwd)
and container and proc.name = bash
or proc.name = bash or proc.name = sh
not not container

Output Field — Alert Format

The output string supports field placeholders:

output: >
  Shell spawned in container
  (user=%user.name container=%container.name image=%container.image.repository
   command=%proc.cmdline namespace=%k8s.ns.name pod=%k8s.pod.name)

Common placeholders:

Placeholder Value
%user.name Username
%container.name Container name
%container.id Container ID
%container.image.repository Image name
%proc.name Process name
%proc.cmdline Full command
%fd.name File path
%k8s.ns.name Namespace
%k8s.pod.name Pod name
%evt.time Event timestamp

Macros — Reusable Condition Snippets

Macros are named condition fragments you can reference in rules:

- macro: container
  condition: container.id != host

- macro: shell_procs
  condition: proc.name in (bash, sh, zsh, csh, ksh, dash, ash)

- macro: sensitive_files
  condition: >
    fd.name startswith /etc and
    (fd.name in (/etc/shadow, /etc/passwd, /etc/sudoers)
     or fd.name startswith /etc/sudoers.d/)

- macro: spawned_process
  condition: evt.type = execve and evt.dir = <

Using macros in a rule:

- rule: Shell in container
  desc: Detect shell spawned inside a container
  condition: spawned_process and container and shell_procs
  output: "Shell spawned (user=%user.name container=%container.name)"
  priority: WARNING

Lists — Reusable Value Sets

Lists define sets of values you can reference in conditions:

- list: trusted_images
  items: [nginx, redis, postgres, mycompany/app]

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

- list: sensitive_file_paths
  items: [/etc/shadow, /etc/passwd, /etc/sudoers, /etc/pam.d]

- list: allowed_k8s_namespaces
  items: [kube-system, monitoring, falco]

Using lists:

- rule: Untrusted image started
  desc: Container started with an image not in the trusted list
  condition: >
    container and evt.type = container and
    not container.image.repository in (trusted_images)
  output: "Untrusted image (image=%container.image.repository pod=%k8s.pod.name)"
  priority: WARNING

Default Rules Every CKS Candidate Must Know

These rules ship with Falco and are the most likely to appear on the exam:

1. Terminal Shell in Container

- rule: Terminal shell in container
  desc: >
    A shell was spawned by a program in a container with an attached terminal.
  condition: >
    spawned_process and container and shell_procs and proc.tty != 0
  output: >
    A shell was spawned in a container with an attached terminal
    (user=%user.name user_loginuid=%user.loginuid container_id=%container.id
     container_name=%container.name image=%container.image.repository
     shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline
     terminal=%proc.tty pid=%proc.pid)
  priority: NOTICE
  tags: [container, shell, mitre_execution]

Triggered by: kubectl exec -it <pod> -- /bin/bash

2. Read Sensitive File Untouched

- rule: Read sensitive file untouched
  desc: Detect reads to sensitive files (e.g., /etc/shadow)
  condition: >
    open_read and sensitive_files and container
    and not proc.name in (allowed_sensitive_readers)
  output: >
    Sensitive file read (user=%user.name command=%proc.cmdline
     file=%fd.name container=%container.name image=%container.image.repository)
  priority: WARNING
  tags: [filesystem, mitre_credential_access]

Triggered by: cat /etc/shadow inside a container

3. Write Below /etc

- rule: Write below etc
  desc: Detect writes to /etc directory inside a container
  condition: >
    open_write and container and fd.name startswith /etc
  output: >
    File written below /etc (user=%user.name command=%proc.cmdline
     file=%fd.name container=%container.name)
  priority: ERROR
  tags: [filesystem, mitre_persistence]

Triggered by: Writing or modifying files in /etc inside a container

4. Privilege Escalation via setuid/setgid

- rule: Non sudo setuid
  desc: Detect setuid calls by non-sudo programs
  condition: >
    evt.type = setuid and evt.dir = < and container
    and not proc.name in (sudo, su)
  output: >
    Privilege escalation via setuid (user=%user.name command=%proc.cmdline
     container=%container.name)
  priority: CRITICAL
  tags: [users, mitre_privilege_escalation]

5. Contact K8S API Server From Container

- rule: Contact K8S API Server From Container
  desc: Detect connections to the Kubernetes API server from a container
  condition: >
    evt.type = connect and container
    and fd.sip.name = "kubernetes.default.svc.cluster.local"
  output: >
    Container contacted K8S API server
    (user=%user.name container=%container.name image=%container.image.repository
     connection=%fd.name namespace=%k8s.ns.name pod=%k8s.pod.name)
  priority: WARNING
  tags: [network, k8s, mitre_discovery]

Writing Custom Rules — Complete Examples

Custom Rule 1: Detect Package Manager Execution

- list: package_managers
  items: [apt, apt-get, yum, dnf, apk, pip, pip3, npm]

- rule: Package manager execution in container
  desc: Detect package manager running inside a container (potential supply chain attack)
  condition: >
    spawned_process and container
    and proc.name in (package_managers)
  output: >
    Package manager executed in container
    (user=%user.name command=%proc.cmdline container=%container.name
     image=%container.image.repository namespace=%k8s.ns.name)
  priority: ERROR
  tags: [container, software_mgmt, mitre_execution]

Custom Rule 2: Detect Reverse Shell

- rule: Reverse shell in container
  desc: Detect reverse shell execution patterns
  condition: >
    spawned_process and container
    and ((proc.name = bash and proc.cmdline contains "/dev/tcp")
      or (proc.name = python and proc.cmdline contains "socket")
      or (proc.name = nc and proc.args contains "-e")
      or (proc.name = ncat and proc.args contains "--exec"))
  output: >
    Reverse shell detected (user=%user.name command=%proc.cmdline
     container=%container.name namespace=%k8s.ns.name pod=%k8s.pod.name)
  priority: CRITICAL
  tags: [container, network, mitre_execution]

Custom Rule 3: Detect Crypto Mining

- list: crypto_mining_domains
  items: [pool.minergate.com, xmr.pool, moneropool, cryptonight]

- list: crypto_mining_processes
  items: [xmrig, minerd, cpuminer, minergate, cryptonight]

- rule: Crypto mining activity
  desc: Detect cryptocurrency mining processes or connections
  condition: >
    (spawned_process and container and proc.name in (crypto_mining_processes))
    or (container and evt.type = connect and fd.sip.name pmatch (crypto_mining_domains))
  output: >
    Crypto mining detected (user=%user.name command=%proc.cmdline
     container=%container.name namespace=%k8s.ns.name)
  priority: CRITICAL
  tags: [container, crypto, mitre_execution]

Custom Rule 4: Detect Service Account Token Access

- rule: Service account token read
  desc: Detect process reading Kubernetes service account token
  condition: >
    open_read and container
    and fd.name startswith /var/run/secrets/kubernetes.io/serviceaccount
    and not proc.name in (curl, wget, kube-proxy)
  output: >
    Service account token accessed (user=%user.name command=%proc.cmdline
     file=%fd.name container=%container.name namespace=%k8s.ns.name)
  priority: WARNING
  tags: [container, k8s, mitre_credential_access]

Custom Rule 5: Detect Namespace-Specific Shell Access

- rule: Shell in production namespace
  desc: Detect shell access in production namespace
  condition: >
    spawned_process and container and shell_procs
    and proc.tty != 0
    and k8s.ns.name = "production"
  output: >
    PRODUCTION ALERT — Shell spawned (user=%user.name container=%container.name
     pod=%k8s.pod.name command=%proc.cmdline)
  priority: CRITICAL
  tags: [container, shell, production]

Overriding Default Rules

You can override a default rule by specifying the same rule name in your custom file with override:

Disable a default rule:

# In /etc/falco/falco_rules.local.yaml
- rule: Terminal shell in container
  enabled: false

Change priority of a default rule:

- rule: Terminal shell in container
  override:
    priority: replace
  priority: CRITICAL

Append to a rule’s condition:

- rule: Terminal shell in container
  override:
    condition: append
  condition: and not k8s.ns.name = "debug-allowed"

This adds an exception: shells in the debug-allowed namespace will not trigger the rule.

Append to a macro:

- macro: shell_procs
  override:
    condition: append
  condition: or proc.name = fish

Append to a list:

- list: trusted_images
  override:
    items: append
  items: [mycompany/custom-app, mycompany/sidecar]

Rule Debugging

Validate rules before restarting Falco:

sudo falco --validate /etc/falco/falco_rules.local.yaml

Run Falco in dry-run mode to test rules:

sudo falco --dry-run -r /etc/falco/falco_rules.local.yaml

List all loaded rules:

sudo falco --list

List rules matching a specific tag:

sudo falco --list --tag container

Putting It All Together — Complete Custom Rules File

Here is a complete /etc/falco/falco_rules.local.yaml:

# Custom lists
- list: trusted_images
  items: [nginx, redis, mycompany/app-v2]

- list: package_managers
  items: [apt, apt-get, yum, dnf, apk, pip, npm]

# Custom macros
- macro: in_production
  condition: k8s.ns.name = "production"

# Override — escalate shell detection severity
- rule: Terminal shell in container
  override:
    priority: replace
  priority: CRITICAL

# Custom rule — detect package managers
- rule: Package manager in container
  desc: Detect package install attempt inside running container
  condition: >
    spawned_process and container
    and proc.name in (package_managers)
  output: >
    Package manager executed (user=%user.name cmd=%proc.cmdline
     container=%container.name image=%container.image.repository
     ns=%k8s.ns.name pod=%k8s.pod.name)
  priority: ERROR
  tags: [container, software_mgmt]

# Custom rule — detect writes to /bin or /sbin
- rule: Write to binary directory
  desc: Detect writes to executable directories in a container
  condition: >
    open_write and container
    and (fd.name startswith /bin or fd.name startswith /sbin
         or fd.name startswith /usr/bin or fd.name startswith /usr/sbin)
  output: >
    Binary directory write (user=%user.name cmd=%proc.cmdline
     file=%fd.name container=%container.name ns=%k8s.ns.name)
  priority: CRITICAL
  tags: [container, filesystem, mitre_persistence]

After editing, always restart Falco:

sudo systemctl restart falco
journalctl -u falco --since "1 minute ago" | head -20

Practice Questions

Question 1

Where should you place custom Falco rules? Why should you never edit the default rules file?

Show Answer

Place custom rules in /etc/falco/falco_rules.local.yaml.

Never edit /etc/falco/falco_rules.yaml because:

  • It is overwritten when Falco is upgraded
  • Custom rules in the local file automatically override rules with the same name in the default file
  • This separation makes it clear what is default vs. custom

Question 2

Write a Falco rule that detects when curl or wget is executed inside any container.

Show Answer
- rule: Network tool in container
  desc: Detect curl or wget execution in a container
  condition: >
    spawned_process and container
    and proc.name in (curl, wget)
  output: >
    Network tool executed (user=%user.name command=%proc.cmdline
     container=%container.name image=%container.image.repository
     namespace=%k8s.ns.name pod=%k8s.pod.name)
  priority: WARNING
  tags: [container, network]

Add to /etc/falco/falco_rules.local.yaml and restart Falco:

sudo systemctl restart falco

Question 3

How do you disable the default “Terminal shell in container” rule without deleting it?

Show Answer

Add the following to /etc/falco/falco_rules.local.yaml:

- rule: Terminal shell in container
  enabled: false

Restart Falco:

sudo systemctl restart falco

Question 4

What is the difference between a Falco macro and a Falco list?

Show Answer
Feature Macro List
What it holds A reusable condition expression (boolean logic) A reusable set of values
Syntax condition: <filter expression> items: [val1, val2, val3]
Used in condition field of rules or other macros in (list_name) inside conditions
Example container.id != host [bash, sh, zsh]

Macro example:

- macro: container
  condition: container.id != host

List example:

- list: shell_binaries
  items: [bash, sh, zsh]

Question 5

You want the “Terminal shell in container” rule to NOT fire for pods in the kube-system namespace. How do you achieve this?

Show Answer

Use the override and append mechanism in /etc/falco/falco_rules.local.yaml:

- rule: Terminal shell in container
  override:
    condition: append
  condition: and not k8s.ns.name = "kube-system"

This appends the exception to the existing condition without replacing the entire rule. Restart Falco after editing:

sudo systemctl restart falco

Next: Syscall Monitoring →

Updated: