Deploying Falco on Kubernetes: Real-Time Container Runtime Intrusion Detection

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Comparing Runtime Security Approaches in Kubernetes

At 2:15 AM last Tuesday, our alert channel woke the entire on-call rotation. An attacker had exploited a path traversal flaw in a customer-facing service, opened an interactive bash session inside an unprivileged pod, and initiated an Nmap sweep across the 10.244.0.0/16 pod CIDR.

Trivy had cleared that identical image just two hours earlier with zero critical CVEs. Static scanning tells you what software vulnerabilities exist on disk. Runtime monitoring tells you what an adversary is actively executing in memory.

Engineering teams tackling container runtime defense typically evaluate three distinct architectures:

  • LSM and Profile Enforcement (AppArmor / Seccomp): Restricting syscalls and Linux capabilities up front by attaching static profiles directly to the container security context.
  • Kubernetes Audit Logging: Streaming kube-apiserver events to OpenSearch or Datadog to capture administrative changes across the control plane.
  • Kernel Tracing Engines (Falco / eBPF): Hooking directly into kernel system calls to inspect processes, socket creations, and filesystem writes the instant they happen.

Trade-offs Across Approaches

1. AppArmor and Seccomp

Pros: Enforces rules at zero telemetry cost. Blocks unauthorized syscalls outright rather than simply pinging an alert channel.

Cons: Managing custom profiles for 80+ rapidly evolving microservices drains engineering time. Block one unexpected clone3 or epoll_pwait syscall, and your production pod crashes silently under traffic spikes.

2. Kubernetes Audit Log Ingestion

Pros: Operates entirely out of band. It places zero agent overhead on worker nodes while reliably tracking RBAC escalation and Secret reads.

Cons: Blind to workload internals. When an attacker delivers a staged binary through an existing TCP socket and executes it, the Kubernetes control plane records nothing.

3. Kernel Tracing with Falco (eBPF)

Pros: Delivers granular visibility into raw system events like execve, connect, and openat. Falco enriches every event with Kubernetes metadata including namespace, pod name, and container hash. It catches zero-day attacks that static CI checks cannot anticipate.

Cons: Requires node-level kernel instrumentation and privileged daemon access. Poorly tuned filters can also spike node memory usage.

Recommended Architecture: Modern eBPF DaemonSet with Falcosidekick

Legacy kernel modules frequently caused headaches. A minor kernel patch on Ubuntu or Amazon Linux often broke driver compilation during node auto-scaling events, occasionally triggering kernel panics under heavy I/O. The modern eBPF probe solves this issue. It uses BTF (BPF Type Format) and CO-RE (Compile Once – Run Everywhere) on Linux kernels 5.8 and newer. You get near-instant startup, stable instrumentation, and a predictable footprint under 1.5% CPU and 150MB of RAM per worker node.

Falco acts as the sensor engine on each node. Falcosidekick sits alongside it as a lightweight multiplexer, routing structured security events to Slack, PagerDuty, or security data lakes.

Before installing runtime agents, ensure your base host access is locked down. For emergency root access and jump-host setup, generate strong node credentials with the client-side password generator at toolcraft.app/en/tools/security/password-generator. It computes passwords entirely in the browser without sending entropy across the network. With baseline access secured, Falco catches anything that slips past the perimeter.

Implementation Guide: Installing and Tuning Falco

Step 1: Register the Falco Helm Repository

Fetch the official charts and update local index caches:

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

Step 2: Configure the Helm Values

Create falco-values.yaml. Configure the modern eBPF probe, enable structured JSON formatting, and point alerts to your team channels via Falcosidekick:

driver:
  kind: modern_ebpf

falco:
  json_output: true
  log_stderr: true
  log_level: notice
  priority: notice
  rules_file:
    - /etc/falco/falco_rules.yaml
    - /etc/falco/falco_rules.local.yaml
    - /etc/falco/rules.d

falcosidekick:
  enabled: true
  webui:
    enabled: true
  config:
    slack:
      webhookurl: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
      minimumpriority: "warning"

Step 3: Deploy the DaemonSet

Install the chart into an isolated namespace:

kubectl create namespace falco
helm install falco falcosecurity/falco \
  --namespace falco \
  --values falco-values.yaml

Confirm every worker node has scheduled an operational probe pod:

kubectl get pods -n falco -o wide

Step 4: Craft Targeted Rules for Production Namespaces

Default community rules flag every terminal shell across the cluster. In practice, background CI runners, sidecar sync scripts, and maintenance jobs will trigger dozens of false positives each day. Target high-value environments instead, such as transaction processing or database tiers.

Define your overrides in custom-rules.yaml:

customRules:
  rules-payments.yaml: |-
    - rule: Unauthorized Shell in Payment Namespace
      desc: Triggers when any shell process starts within the payments workload boundary
      condition: >
        spawned_process and
        container and
        k8s.ns.name = "payments" and
        shell_procs
      output: >
        CRITICAL: Shell initiated in restricted workload
        (user=%user.name cmd=%proc.cmdline pod=%k8s.pod.name
        ns=%k8s.ns.name container=%container.name image=%container.image.repository)
      priority: CRITICAL
      tags: [container, execution, pci_dss]

Push the updated configuration using Helm upgrade:

helm upgrade falco falcosecurity/falco \
  --namespace falco \
  --reuse-values \
  -f custom-rules.yaml

Step 5: Validate Threat Detection

Never assume an alert pipeline works until you see it fire. Spawn a lightweight test container inside the target namespace and execute a shell session:

kubectl run validation-target --image=alpine:3.19 --namespace payments -- sleep 600
kubectl exec -it validation-target --namespace payments -- /bin/sh

Tail the active Falco log stream from a second shell window:

kubectl logs -n falco -l app.kubernetes.io/name=falco -c falco --tail=10

Falco surfaces the event within 15 milliseconds, formatting the output into clean JSON:

{
  "output": "CRITICAL: Shell initiated in restricted workload (user=root cmd=/bin/sh pod=validation-target ns=payments container=validation-target image=alpine:3.19)",
  "priority": "Critical",
  "rule": "Unauthorized Shell in Payment Namespace",
  "time": "2026-10-25T02:18:12.312918451Z"
}

Step 6: Eliminate Alert Fatigue with Macro Exceptions

Security tools that generate noise end up muted. If an automated backup CronJob legitimately runs a shell script at 3:00 AM, do not disable the detection rule. Append a macro filter to exempt the specific binary and execution path:

- macro: allowed_backup_executions
  condition: >
    (proc.cmdline startswith "/opt/scripts/backup.sh" and
     k8s.pod.name startswith "postgres-backup-")

- rule: Unauthorized Shell in Payment Namespace
  condition: >
    spawned_process and
    container and
    k8s.ns.name = "payments" and
    shell_procs and
    not allowed_backup_executions

Review alerts daily during your first two weeks. Fine-tuning macro boundaries transforms Falco from a noisy log generator into an authoritative, low-latency intrusion detection barrier.

Share: