Post

Week 2 — Day 13: Runtime Security with Falco

A full walkthrough of Falco — syscall-based runtime threat detection for containers and Kubernetes, writing custom rules, and forwarding alerts to Slack or a SIEM.

Week 2 — Day 13: Runtime Security with Falco

The Gap That Scanning Misses

Trivy scans images for known CVEs. RBAC restricts who can deploy what. But neither tells you what’s happening inside a running container right now.

What if a container starts a shell? What if it reads /etc/shadow? What if it opens a network connection to an unusual IP? What if a cryptominer process spawns?

That’s what Falco detects — suspicious behavior at the system call level, in real time.


How Falco Works

Falco runs as a privileged daemonset on each Kubernetes node (or as a host process). It hooks into the Linux kernel via:

  • eBPF probe — modern, recommended (no kernel module needed)
  • Kernel module — traditional approach
  • /proc filesystem — minimal, fallback option

Every system call made by every container on the node is evaluated against Falco’s rule set. When a call matches a rule, Falco fires an alert.

[SCREENSHOT]Falco architecture diagram or the Falco pods running: kubectl get pods -n falco showing a falco pod on each node (DaemonSet)


Installing Falco on Kubernetes

1
2
3
4
5
6
7
8
9
10
11
# Add the Falco Helm repo
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

# Install Falco with eBPF driver
helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set driver.kind=ebpf \
  --set falcosidekick.enabled=true \
  --set falcosidekick.webui.enabled=true

[SCREENSHOT]Terminal showing helm install falco completing successfully, followed by kubectl get pods -n falco showing falco and falcosidekick pods running

Verify Falco is Running

1
kubectl logs -n falco -l app.kubernetes.io/name=falco | head -30

[SCREENSHOT]kubectl logs output showing Falco starting up, loading rules files, and printing “Falco initialized, ready to help”


Understanding Falco Rules

Rules are written in YAML and describe patterns to detect.

Rule Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- rule: Shell Spawned in Container
  desc: A shell was spawned in a container — could indicate an attacker exploring the environment
  condition: >
    spawned_process and
    container and
    shell_procs and
    not container.image.repository in (trusted_images)
  output: >
    Shell spawned in container
    (user=%user.name user_loginuid=%user.loginuid
     container=%container.name
     image=%container.image.repository:%container.image.tag
     shell=%proc.name parent=%proc.pname
     cmdline=%proc.cmdline)
  priority: WARNING
  tags: [container, shell, mitre_execution]

Key fields: | Field | Purpose | |——-|———| | condition | Boolean expression using Falco fields and macros | | output | Log message when the rule fires | | priority | DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY | | tags | For categorization and filtering |


Default Rules — What Falco Catches Out of the Box

Falco ships with a rich default ruleset. Key rules:

RuleWhat it detects
Terminal shell in containerInteractive shell started in a running container
Write below binary dirWriting to /bin, /sbin, /usr/bin etc. in a container
Read sensitive file untrustedContainer reading /etc/shadow, /etc/sudoers etc.
Contact K8S API Server From ContainerContainer calling the Kubernetes API
Launch Privileged ContainerA privileged container is started
Outbound Connection to C2 ServersConnection to known C2 IP addresses
Crypto Mining ActivityProcesses associated with crypto mining tools
Modify binary dirsFiles written to system binary directories
Container Drift DetectedA new executable is created in a running container (ELF file)

Triggering Rules — Live Demo

Trigger “Terminal shell in container”

1
2
3
4
5
# In one terminal, exec into a running container
kubectl exec -it <pod-name> -- /bin/sh

# In another terminal, watch Falco logs
kubectl logs -n falco -l app.kubernetes.io/name=falco -f

[SCREENSHOT]kubectl logs -n falco showing a Falco alert firing: “Warning Terminal shell in container (user=root container=myapp image=myapp:latest shell=sh)” with timestamp

Trigger “Read sensitive file”

1
2
# Inside the container shell
cat /etc/shadow

[SCREENSHOT]Falco log showing “Warning Sensitive file opened for reading (user=root file=/etc/shadow container=myapp)”

Trigger “Write below binary dir”

1
2
# Inside the container
touch /bin/malware

[SCREENSHOT]Falco log showing “Error Write below binary dir (user=root file=/bin/malware container=myapp)”


Writing Custom Rules

Detect Unusual Outbound Connections

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- rule: Unexpected Outbound Connection
  desc: A container is making an outbound connection to an unexpected destination
  condition: >
    outbound and
    container and
    not fd.sport in (80, 443, 5432, 6379) and
    not container.image.repository in (trusted_network_images)
  output: >
    Unexpected outbound connection from container
    (container=%container.name
     image=%container.image.repository
     connection=%fd.name
     proc=%proc.name)
  priority: WARNING

Detect Crypto Mining by CPU Behavior

1
2
3
4
5
6
7
8
9
10
11
12
13
- rule: Crypto Mining Process
  desc: Likely crypto mining activity detected
  condition: >
    spawned_process and
    container and
    proc.name in (xmrig, minergate, ethminer, cgminer, bfgminer)
  output: >
    Crypto mining process detected
    (container=%container.name
     image=%container.image.repository
     proc=%proc.name
     cmdline=%proc.cmdline)
  priority: CRITICAL

Detect Container Drift (New Executables)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- rule: New Executable Dropped in Container
  desc: A new ELF binary was created in a running container — potential malware drop
  condition: >
    container and
    evt.type=write and
    fd.typechar=f and
    (fd.name startswith /bin/ or fd.name startswith /usr/bin/ or fd.name startswith /tmp/) and
    spawned_process
  output: >
    New binary dropped in container
    (container=%container.name
     file=%fd.name
     user=%user.name)
  priority: CRITICAL

Falco Fields Reference

Useful fields for writing conditions and output:

FieldValue
container.nameContainer name
container.image.repositoryImage name
container.image.tagImage tag
proc.nameProcess name
proc.cmdlineFull command line
proc.pnameParent process name
user.nameUsername
fd.nameFile/socket name
fd.sportSource port
fd.dportDestination port
k8s.pod.nameKubernetes pod name
k8s.ns.nameKubernetes namespace

Falco Sidekick — Forwarding Alerts

Falco outputs to stdout by default. Falco Sidekick routes alerts to external systems.

Supported outputs include: Slack, PagerDuty, Splunk, Elasticsearch, Teams, Datadog, AWS SNS, and 50+ others.

Configure Slack Output

1
2
3
4
5
helm upgrade falco falcosecurity/falco \
  --namespace falco \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/services/YOUR/WEBHOOK/URL" \
  --set falcosidekick.config.slack.minimumpriority="warning"

[SCREENSHOT]Slack channel showing a Falco alert message with container name, image, rule name, and severity — formatted as a Slack message card

Falco Sidekick UI

1
2
3
# Port-forward the Sidekick UI
kubectl port-forward svc/falco-falcosidekick-ui 2802:2802 -n falco
# Open http://localhost:2802

[SCREENSHOT]Falco Sidekick UI in browser showing a dashboard with alert counts by priority, a timeline of recent alerts, and a table of the latest events with rule names, containers, and priorities


Falco in AWS EKS

When running on EKS, some additional setup is needed since managed node groups don’t allow kernel module installation. Use the eBPF driver:

1
2
3
4
5
helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set driver.kind=ebpf \
  --set ebpf.enabled=true

For Fargate (no node access), Falco cannot run — it requires access to the host kernel. Use Sysdig or AWS-native options instead.


Lab — Install Falco and Trigger a Rule

  1. Install Falco via Helm on your cluster:
    1
    2
    
    helm repo add falcosecurity https://falcosecurity.github.io/charts && helm repo update
    helm install falco falcosecurity/falco -n falco --create-namespace --set driver.kind=ebpf
    
  2. Wait for the DaemonSet to be ready:
    1
    
    kubectl rollout status daemonset/falco -n falco
    
  3. Start watching logs:
    1
    
    kubectl logs -n falco -l app.kubernetes.io/name=falco -f
    
  4. In another terminal, exec into any running pod and open a shell:
    1
    
    kubectl exec -it <any-pod> -- /bin/sh
    

[SCREENSHOT]Two terminals side by side: left showing kubectl exec into a pod, right showing the Falco log immediately printing the “Terminal shell in container” warning alert

  1. Inside the shell, try to read a sensitive file:
    1
    
    cat /etc/shadow 2>/dev/null || echo "file not found"
    

[SCREENSHOT]Falco log showing the “Read sensitive file untrusted” alert firing with the container name and filename

  1. Exit the shell — confirm no more alerts fire after exiting

Key Takeaways

  • Falco detects what’s happening inside containers right now — scanning tools can’t do this
  • The default ruleset covers the most common attack patterns — enable it and you’re already ahead
  • Write custom rules for your environment — unusual ports, specific process names, sensitive file paths
  • Falco Sidekick routes alerts to your team’s communication and SIEM tools
  • Exec’ing into a production container should always fire a Falco alert — if it doesn’t, something’s wrong
  • Combine Falco (runtime) + Trivy (build time) + RBAC (access control) for defense in depth

References


You can find me online at:

My signature image

This post is licensed under CC BY 4.0 by the author.