Lo scan di un’immagine e l’analisi statica dei manifest Kubernetes verificano com’è fatto un artefatto prima del deploy, ma non guardano cosa fa un processo mentre gira. Falco guarda proprio lì: legge il flusso di system call del kernel Linux a runtime e segnala quando un processo dentro un container fa qualcosa che le regole descrivono come sospetto.

Il progetto è nato in Sysdig nel 2016 sopra la stessa strumentazione del kernel che usa il tool sysdig, ed è entrato nel CNCF Sandbox il 10 ottobre 2018 — primo strumento di runtime security accettato nella Sandbox della Cloud Native Computing Foundation. La licenza è Apache 2.0. La versione corrente è la 0.13.0, uscita il 12 novembre 2018.

Problema

Quasi tutti i controlli di sicurezza sui container sono statici: vulnerability scan dell’immagine, verifica delle firme, policy di ammissione, controllo dei privilegi richiesti nel manifest. Tutti rispondono alla domanda “questo artefatto è accettabile?” prima che parta. Nessuno risponde a “questo processo, adesso, sta facendo qualcosa che non dovrebbe?”.

A runtime emergono i comportamenti che lo scan non può prevedere: una shell aperta dentro un container di produzione, la lettura di /etc/shadow, un binario che apre una connessione verso un host non previsto, un tentativo di montare il filesystem dell’host. Dipendono dall’esecuzione, non da come è composta l’immagine, e quindi sfuggono a qualunque analisi pre-deploy.

Architettura

Falco osserva le system call — le operazioni che ogni processo deve passare al kernel per leggere un file, aprire un socket, eseguire un altro programma. La cattura avviene nel kernel tramite due meccanismi alternativi che alimentano lo stesso flusso di eventi:

  • un kernel module, l’approccio primario, che inserisce la strumentazione direttamente nel kernel;
  • una sonda eBPF, alternativa che esegue il codice di cattura nella sandbox eBPF del kernel senza caricare un modulo, a fronte di un overhead un po’ più alto.

Gli eventi passano in userspace e vengono valutati contro un insieme di regole. Una regola si scrive in YAML, con una condizione espressa nel linguaggio di filtraggio ereditato da sysdig, vicino come sintassi a quello di 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

Quando una condizione corrisponde a un evento, Falco emette un alert sul canale configurato: stdout, syslog, un file, oppure un programma esterno a cui delegare l’inoltro. La distribuzione porta con sé un set di regole di base che copre gli scenari ricorrenti — spawn di shell nei container, accesso a file sensibili, scritture in directory di sistema, attività di rete inattesa, tentativi di escape — e lo si estende con regole proprie, macro riutilizzabili e list di valori.

Punto critico

La novità di peso della 0.13.0 è strutturale: Falco smette di dipendere da una sola sorgente di eventi. Fino alla versione precedente l’unico flusso erano le system call; ora ogni regola dichiara la propria sorgente con l’attributo source, che vale syscall di default oppure k8s_audit per gli audit event dell’API server di Kubernetes.

Per riceverli, Falco espone un webserver basato su civetweb che ascolta su una porta configurabile e accetta richieste POST su un endpoint dedicato; l’audit backend di Kubernetes va configurato per mandare gli eventi lì. Le regole corrispondenti stanno in k8s_audit_rules.yaml e lavorano su un dominio diverso dalle syscall: creazione di pod privilegiati, ruoli RBAC troppo permissivi, secret con credenziali in chiaro. La regola di esempio della release ispeziona il campo requestObject di un audit event per scovare un ConfigMap che incorpora chiavi AWS o password.

Ne segue che lo stesso motore osserva due piani distinti con la stessa grammatica di regole: cosa fanno i processi sui nodi e cosa viene chiesto al control plane. Un pod privilegiato che parte e una shell che si apre dentro quel pod diventano eventi valutati dallo stesso strumento, anche se arrivano da percorsi indipendenti.

Implicazioni

Sul piano operativo Falco si dispiega in Kubernetes come DaemonSet, un’istanza per nodo, così osserva le syscall di tutti i container che insistono su quel kernel. Un secondo canale, alimentato dall’audit dell’API server, copre invece le richieste al control plane e non è per nodo.

Il modello di detection è descrittivo e non addestrato: ogni regola dice in modo esplicito quale combinazione di processo, file, container e contesto vale come evento da segnalare. L’output resta leggibile — l’alert riporta la condizione che ha fatto match e i campi rilevanti — e il falso positivo diventa un problema di regola, che si corregge modificando la condizione, anziché un comportamento opaco da ricalibrare. In cambio, la copertura coincide con le regole scritte: ciò che nessuna condizione descrive non viene visto.

Limiti

Falco osserva e segnala; non blocca. Un alert dice che qualcosa è successo, non lo impedisce; l’azione conseguente — terminare un pod, isolare un nodo, aprire un ticket — sta a valle e va costruita a parte.

La fedeltà del flusso dipende dalla strumentazione del kernel: kernel module e sonda eBPF vanno costruiti o reperiti per la versione di kernel in uso, e un ambiente che ne impedisce il caricamento lascia Falco senza la sua sorgente principale. Le regole di base sono un punto di partenza generico: messe così com’è su un carico reale fanno rumore, perché molte azioni che le condizioni standard segnalano come anomale sono legittime per una specifica applicazione. E un avversario che conosce le regole può scegliere percorsi che nessuna condizione copre — la detection vale quanto la regola che la esprime.


Immagine di copertina: Diagramma a blocchi dell’interfaccia delle system call del kernel Linux, con la GNU C Library (glibc) in spazio utente che fa da… — diagramma di Shmuel Csaba Otto Traian, CC BY-SA 3.0 — https://commons.wikimedia.org/wiki/File:Linux_kernel_System_Call_Interface_and_glibc.svg