Je scande je images met Trivy. Je dwingt policies af met Kyverno. Je workloads hebben cryptografische identiteit via SPIFFE.

Maar wat gebeurt er na deployment? Wat als een container wordt gecompromitteerd tijdens runtime? Wat als een aanvaller een zero-day exploiteert?

Preventie is niet genoeg. Je hebt detectie nodig.

Falco is een runtime security tool die system calls monitort in je cluster. Het ziet alles wat containers doen — bestandstoegang, netwerkverbindingen, procesuitvoering — en alert wanneer iets er verkeerd uitziet.

Waarom Runtime Security?

Security heeft lagen:

  1. Build time — Scan images voor bekende kwetsbaarheden (Trivy)
  2. Deploy time — Dwing policies af (Kyverno, admission controllers)
  3. Runtime — Detecteer afwijkend gedrag (Falco)

De meeste teams focussen op lagen 1 en 2. Maar aanvallers geven niet om je pipeline — ze exploiteren wat draait.

Runtime voorbeelden die andere tools missen:

  • Container voert /bin/bash uit (zou niet moeten in productie)
  • Proces leest /etc/shadow
  • Onverwachte uitgaande verbinding naar crypto mining pool
  • Container schrijft naar /etc/ directories
  • Kubernetes secrets benaderd vanuit ongewone pods

Falco vangt deze omdat het daadwerkelijk gedrag monitort, niet alleen configuratie.

Hoe Falco Werkt

Falco gebruikt eBPF (of een kernel module) om system calls te intercepten:

flowchart TD
    subgraph userspace["User Space"]
        A["Container A"]
        B["Container B"]
        C["Container C"]
    end

    subgraph kernel["Kernel Space"]
        EBPF["eBPF / Kernel Module<br/>(intercepts syscalls)"]
    end

    A --> EBPF
    B --> EBPF
    C --> EBPF

    EBPF --> FALCO["Falco<br/>(rules engine)"]
    FALCO --> ALERTS["Alerts<br/>(stdout, webhook, Kafka...)"]

Elke syscall wordt geëvalueerd tegen rules. Matchende syscalls genereren alerts.

Falco Installeren

Met Helm en eBPF driver (aanbevolen):

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

helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set driver.kind=ebpf \
  --set falcosidekick.enabled=true

Voor GitOps met ArgoCD:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: falco
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://falcosecurity.github.io/charts
    chart: falco
    targetRevision: 4.0.0
    helm:
      values: |
        driver:
          kind: ebpf
        falcosidekick:
          enabled: true
          config:
            slack:
              webhookurl: "https://hooks.slack.com/services/xxx"
        tty: true
  destination:
    server: https://kubernetes.default.svc
    namespace: falco
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Falco Rules Begrijpen

Rules definiëren welk gedrag te detecteren. De syntax:

- rule: Terminal shell in container
  desc: Detect shell being spawned in a container
  condition: >
    spawned_process and
    container and
    shell_procs and
    proc.tty != 0
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name shell=%proc.name
     parent=%proc.pname cmdline=%proc.cmdline)
  priority: WARNING
  tags: [container, shell, mitre_execution]

Belangrijke delen:

  • condition — Wanneer te triggeren (met Falco’s filter taal)
  • output — Wat op te nemen in de alert
  • priority — Severity level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  • tags — Voor categorisatie en filtering

Ingebouwde Rules

Falco komt met uitgebreide default rules:

# Bekijk geladen rules
kubectl exec -n falco -it falco-xxx -- falco --list

Categorieën omvatten:

  • Container escape pogingen
  • Privilege escalation
  • Gevoelige bestandstoegang
  • Verdachte netwerkactiviteit
  • Kubernetes API misbruik

Voorbeeld ingebouwde rules:

# Detecteer container escape via mount
- rule: Launch Sensitive Mount Container
  condition: >
    spawned_process and container and
    sensitive_mount

# Detecteer lezen van gevoelige bestanden
- rule: Read sensitive file untrusted
  condition: >
    open_read and sensitive_files and
    not trusted_containers

# Detecteer crypto mining
- rule: Detect outbound connections to crypto mining pools
  condition: >
    outbound and
    fd.sip.name in (cryptomining_pool_domains)

Custom Rules

Voeg eigen rules toe voor applicatie-specifieke detectie:

# custom-rules.yaml
customRules:
  rules-custom.yaml: |-
    # Alert wanneer onze API server onverwachte processen spawnt
    - rule: Unexpected process in api-server
      desc: Detect processes other than the main app in api-server containers
      condition: >
        spawned_process and
        container.image.repository contains "api-server" and
        not proc.name in (api-server, node, npm)
      output: >
        Unexpected process in api-server
        (command=%proc.cmdline container=%container.name image=%container.image.repository)
      priority: WARNING

    # Alert op database verbinding van onverwachte pods
    - rule: Database connection from non-backend pod
      desc: Detect connections to PostgreSQL from pods that shouldn't connect
      condition: >
        outbound and
        fd.sport = 5432 and
        not k8s.pod.label.app in (api-server, worker, migration-job)
      output: >
        Unexpected database connection
        (pod=%k8s.pod.name namespace=%k8s.ns.name dest=%fd.sip)
      priority: ERROR

Deploy custom rules via Helm values:

# values.yaml
customRules:
  rules-custom.yaml: |-
    - rule: My custom rule
      ...

Falco’s Filter Taal

De filter taal begrijpen is essentieel voor effectieve rules.

Classes en Fields

# Process fields
proc.name         # Proces naam
proc.cmdline      # Volledige command line
proc.pname        # Parent proces naam
proc.exepath      # Executable pad

# Container fields
container.name    # Container naam
container.id      # Container ID
container.image.repository  # Image naam

# File fields
fd.name           # File descriptor naam (bestandspad)
fd.directory      # Directory van het bestand

# Network fields
fd.sip            # Server IP
fd.sport          # Server poort
fd.cip            # Client IP

# Kubernetes fields
k8s.pod.name      # Pod naam
k8s.ns.name       # Namespace
k8s.pod.label.app # Pod label

Macros

Herbruikbare conditie fragmenten:

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

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

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

- macro: open_read
  condition: evt.type in (open, openat) and evt.is_open_read = true

Lijsten

Benoemde sets van waarden:

- list: sensitive_files
  items: [/etc/shadow, /etc/sudoers, /etc/pam.d, /root/.ssh]

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

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

Praktijkvoorbeelden van Detectie

Detecteer Reverse Shells

- rule: Reverse shell detected
  desc: Detect reverse shells via common patterns
  condition: >
    spawned_process and
    container and
    ((proc.name = bash and proc.args contains ">&" and proc.args contains "/dev/tcp") or
     (proc.name = nc and proc.args contains "-e") or
     (proc.name = python and proc.args contains "socket" and proc.args contains "subprocess"))
  output: >
    Reverse shell detected (user=%user.name command=%proc.cmdline container=%container.name)
  priority: CRITICAL
  tags: [mitre_execution, reverse_shell]

Detecteer Kubernetes Secret Toegang

- rule: K8s secret accessed from unexpected namespace
  desc: Detect when secrets are read from non-standard locations
  condition: >
    open_read and
    fd.name startswith "/var/run/secrets/kubernetes.io" and
    not k8s.ns.name in (kube-system, monitoring, vault)
  output: >
    K8s secret accessed (file=%fd.name pod=%k8s.pod.name namespace=%k8s.ns.name)
  priority: WARNING

Detecteer Package Installatie op Runtime

- rule: Package manager in container
  desc: Detect package installations in running containers
  condition: >
    spawned_process and
    container and
    proc.name in (apt, apt-get, yum, dnf, apk, pip, npm) and
    not container.image.repository in (allowed_builder_images)
  output: >
    Package manager run in container
    (command=%proc.cmdline container=%container.name image=%container.image.repository)
  priority: ERROR
  tags: [mitre_persistence, package_install]

Falco Sidekick: Alert Routing

Falco output alerts naar stdout standaard. Falcosidekick routeert alerts naar diverse bestemmingen:

falcosidekick:
  enabled: true
  config:
    slack:
      webhookurl: "https://hooks.slack.com/services/xxx"
      minimumpriority: warning

    prometheus:
      enabled: true

    elasticsearch:
      hostport: "elasticsearch.logging:9200"
      index: "falco"

    alertmanager:
      hostport: "http://alertmanager.monitoring:9093"

    webhook:
      address: "http://security-responder.security:8080/falco"

Dit stuurt:

  • Warnings en hoger naar Slack
  • Alle alerts als Prometheus metrics
  • Alle alerts naar Elasticsearch voor doorzoeken
  • Critical alerts naar Alertmanager voor paging
  • Alles naar een custom webhook voor geautomatiseerde response

Integratie met Prometheus

Falcosidekick stelt Prometheus metrics beschikbaar:

# ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: falco-sidekick
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: falcosidekick
  endpoints:
    - port: http

Maak alerts gebaseerd op Falco detecties:

# PrometheusRule
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: falco-alerts
spec:
  groups:
    - name: falco
      rules:
        - alert: FalcoCriticalAlert
          expr: increase(falco_events{priority="Critical"}[5m]) > 0
          for: 0m
          labels:
            severity: critical
          annotations:
            summary: "Falco critical security event detected"
            description: "{{ $labels.rule }} triggered in {{ $labels.k8s_ns_name }}/{{ $labels.k8s_pod_name }}"

Ruis Verminderen

Default rules kunnen luidruchtig zijn. Stem ze af:

Rules Uitschakelen

# values.yaml
falco:
  rules_file:
    - /etc/falco/falco_rules.yaml
    - /etc/falco/falco_rules.local.yaml  # Overrides
    - /etc/falco/rules.d  # Custom rules

customRules:
  falco_rules.local.yaml: |-
    # Schakel luidruchtige rule uit
    - rule: Terminal shell in container
      enabled: false

    # Wijzig bestaande rule om bepaalde containers uit te sluiten
    - rule: Read sensitive file untrusted
      condition: >
        open_read and sensitive_files and
        not trusted_containers and
        not container.image.repository in (my-trusted-app)

Macros Afstemmen

customRules:
  falco_rules.local.yaml: |-
    # Voeg toe aan trusted containers
    - macro: trusted_containers
      condition: >
        (container.image.repository in (
          my-company/trusted-app,
          my-company/another-app
        ))

    # Sluit namespaces uit van monitoring
    - macro: user_namespace
      condition: >
        k8s.ns.name not in (kube-system, falco, monitoring)

Mijn Productie Configuratie

driver:
  kind: ebpf
  ebpf:
    hostNetwork: true

falco:
  grpc:
    enabled: true
  grpc_output:
    enabled: true
  json_output: true
  log_level: info

  rules_file:
    - /etc/falco/falco_rules.yaml
    - /etc/falco/falco_rules.local.yaml
    - /etc/falco/rules.d

falcosidekick:
  enabled: true
  replicaCount: 2
  config:
    slack:
      webhookurl: "${SLACK_WEBHOOK}"
      minimumpriority: error

    prometheus:
      enabled: true

    alertmanager:
      hostport: "http://alertmanager.monitoring:9093"
      minimumpriority: critical

customRules:
  rules-custom.yaml: |-
    # Onze applicatie-specifieke rules
    - rule: Unexpected outbound connection from backend
      desc: Backend pods should only connect to known services
      condition: >
        outbound and
        k8s.pod.label.app = "backend" and
        not fd.sip.name in (postgres.db, redis.cache, api.internal)
      output: >
        Backend made unexpected outbound connection
        (pod=%k8s.pod.name dest=%fd.sip:%fd.sport)
      priority: WARNING

  falco_rules.local.yaml: |-
    # Stem default rules af voor onze omgeving
    - macro: user_known_write_etc_conditions
      condition: >
        (container.image.repository = "my-company/config-manager")

Belangrijke beslissingen:

  • eBPF driver — Betere performance dan kernel module
  • JSON output — Makkelijker parsen voor downstream tools
  • Getrapte alerting — Errors naar Slack, Critical naar PagerDuty
  • Custom rules — Applicatie-bewuste detectie

Response Automatisering

Falco detecteert; jij bepaalt wat te doen. Voorbeeld geautomatiseerde responses:

# security-responder deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: security-responder
spec:
  template:
    spec:
      containers:
        - name: responder
          image: my-company/security-responder
          env:
            - name: SLACK_WEBHOOK
              valueFrom:
                secretKeyRef:
                  name: security-responder
                  key: slack-webhook

Response script voorbeeld:

@app.route('/falco', methods=['POST'])
def handle_falco_alert():
    alert = request.json

    if alert['priority'] == 'Critical':
        # Kill de pod direct
        pod = alert['output_fields']['k8s.pod.name']
        namespace = alert['output_fields']['k8s.ns.name']

        # Delete pod (Deployment zal hem herstellen)
        v1.delete_namespaced_pod(pod, namespace)

        # Notificeer
        send_slack(f"Pod {namespace}/{pod} gekilld vanwege: {alert['rule']}")

    return 'ok'

Waarom Dit Ertoe Doet

Je kunt niet beveiligen wat je niet kunt zien.

Statische analyse vangt bekende kwetsbaarheden. Admission control dwingt policies af. Maar runtime security ziet daadwerkelijk gedrag — wat er echt gebeurt in je cluster, nu.

Wanneer iets onverwachts gebeurt:

  • Zonder Falco: Je weet het niet totdat de schade is aangericht
  • Met Falco: Je ziet het terwijl het gebeurt

Dit is begrip toegepast op security. Niet hopen dat je preventie werkt, maar weten wat er daadwerkelijk gebeurt en direct reageren.


Preventie is belangrijk, maar detectie is essentieel. Falco geeft je ogen in je containers — zien wat ze doen, niet alleen wat ze zouden moeten doen.