Un modello per realizzare l’hardening dei container: le fasi operative
La maggior parte delle implementazioni cloud-native sono formate da applicazioni complesse strutturate come un insieme di micro-servizi che interagiscono tra loro. Questo modello di deployment, però, è suscettibile alle minacce derivanti dal rischio della supply chain. Ecco come realizzare un processo di hardening.
L’attuale modello di sviluppo cloud-native si basa su una combinazione di principi, tecnologie e procedure che mira a realizzare applicazioni ottimizzate per l’ambiente cloud vale a dire che sia in grado di sfruttare appieno i vantaggi offerti in termini di scalabilità, resilienza, efficienza e agilità.
Dal punto di vista architetturale, gli elementi caratterizzanti un’implementazione cloud sono i seguenti:
- Microservizi: si tratta di un approccio architetturale per lo sviluppo di applicazioni software in cui l’applicazione viene scomposta in piccoli servizi indipendenti e “loosely coupled”, ognuno dei quali esegue una specifica funzione aziendale e comunica con gli altri servizi tramite API leggere e ben definite. Questo facilita lo sviluppo, il deployment e la scalabilità delle applicazioni.
- API-first: è un metodo di sviluppo del software in cui le Application Programming Interface (API) sono considerate la componente fondamentale del progetto e, pertanto, vengono progettate e sviluppate prima di qualsiasi altra parte dell’applicazione, come ad esempio l’interfaccia utente o la logica di business, al fine di garantire l’interoperabilità e la flessibilità.
- Containerizzazione: è un metodo di virtualizzazione light, implementato a livello di sistema operativo, che consente di impacchettare e isolare un’applicazione con tutte le sue dipendenze, librerie e configurazioni in un’unica unità eseguibile chiamata “container”. L’incapsulamento delle applicazioni e delle loro dipendenze in container leggeri e portabili garantisce la consistenza e l’isolamento.
- Orchestrazione dei container: rappresenta la strategia per realizzare l’automazione della gestione del ciclo di vita dei container, include il deployment, lo scaling, il networking e gli aggiornamenti, tramite piattaforme come Kubernetes. I principali vantaggi che ne derivano sono evidenti nel momento in cui i container operano su larga scala, integrate con applicazioni complesse e distribuite su più host.
- Serverless: è un modello di esecuzione del cloud in cui il provider di servizi cloud (come AWS, Google Cloud o Azure) si assume la responsabilità della gestione dell’infrastruttura sottostante, sfruttando servizi come AWS Lambda o Google Cloud Functions. Ciò permette agli sviluppatori di concentrarsi esclusivamente sulla scrittura e l’implementazione del codice senza dover gestire l’infrastruttura. Nonostante il nome, i server sono ancora presenti, ma astratti per lo sviluppatore.
In sintesi, i software in ambiente cloud vengono distribuiti sottoforma di container (c.d. container images), al cui interno sono impacchettate l’applicazione e le sue dipendenze, e divengono fruibili tramite interfacce API. Ogni container image può rappresentare un microservizio o un’intera applicazione che interagisce con le altre images attraverso le piattaforme di orchestrazione.
Principali minacce
Le immagini container, pur offrendo numerosi vantaggi in termini di efficienza e portabilità, presentano diverse vulnerabilità di sicurezza che possono essere sfruttate dai malintenzionati per accedere ai dati, modificare il risultato dell’elaborazione o impedire il corretto funzionamento.
Ecco una disamina dettagliata delle principali minacce riscontrabili:
- Vulnerabilità del software:
- Dipendenze obsolete: spesso le immagini includono librerie e pacchetti software con vulnerabilità note (CVE). Queste possono essere sfruttate per ottenere l’accesso al container e al sistema host.
- Codice applicativo vulnerabile: la stessa applicazione, all’interno dell’immagine, può contenere bug di sicurezza, come injection SQL, cross-site scripting (XSS) o buffer overflow.
- Configurazioni errate: possono essere presenti errori nella configurazione del software, come le password deboli o i permessi eccessivi, che possono esporre il container ad attacchi.
- Immagini container malevole o compromesse:
- Immagini da fonti non attendibili: l’eventualità di scaricare immagini da registri pubblici non verificati può comportare l’introduzione di malware nel sistema.
- Immagini compromesse: è possibile che anche le immagini provenienti da fonti affidabili possano essere state modificate e contenere codice malevolo.
- Attacchi “man-in-the-middle”: si può verificare l’ipotesi che durante il download di un’immagine, un attaccante possa intercettare la comunicazione e sostituire l’immagine originale con una compromessa.
- Problemi di sicurezza del runtime:
- Escape dal container: la presenza di vulnerabilità nel kernel o nel runtime del container possono consentire a un attaccante di uscire dal container e ottenere l’accesso al sistema host.
- Privilege escalation: un attaccante potrebbe sfruttare eventuali vulnerabilità presenti per ottenere privilegi elevati all’interno del container e compromettere l’intero sistema.
- Denial of Service (DoS): gli attacchi DoS possono sovraccaricare le risorse del container o del sistema host, rendendoli indisponibili.
- Mancanza di visibilità e controllo:
- Scarsa gestione delle immagini: spesso per pigrizia o indisponibilità di tempo, non viene effettuato l’inventario completo delle immagini utilizzate e delle loro dipendenze, ciò rende difficile l’identificazione e la mitigazione delle vulnerabilità del software.
- Difficoltà nel monitoraggio: il monitoraggio in tempo reale dei container rappresenta un’attività complessa e onerosa, di conseguenza, diventa difficile rilevare attacchi in corso.
- Problemi legati al ciclo di vita del software:
- Mancanza di aggiornamenti: se le immagini container non venissero aggiornate regolarmente con le patch di sicurezza, potrebbero diventare obsolete e vulnerabili.
- Gestione delle vulnerabilità: è fondamentale disporre di un processo per identificare, valutare e mitigare le vulnerabilità nelle immagini container durante tutto il loro ciclo di vita.
Processo di hardening
Come tutti i software, anche quelli sviluppati e distribuiti tramite la tecnologia dei container, possono essere suscettibili di errore e presentare delle vulnerabilità che si potrebbero propagare nella relativa supply chain.
Di conseguenza, le immagini dei container richiedono un’attività di hardening[1] per eliminare le vulnerabilità presenti.
Alcune ricerche hanno rilevato che i fruitori delle immagini di container incontrano grosse difficoltà sia per realizzare l’hardening, che per sviluppare processi in grado di mitigare le vulnerabilità presenti.
L’uso di immagini di container non verificate aumenta i rischi per la sicurezza poiché consente l’introduzione di componenti potenzialmente vulnerabili nella supply chain del software di un’organizzazione. Per esempio, le immagini dei container non verificate potrebbero contenere pacchetti con Common Vulnerabilities and Exposures (CVE) già note e introdurre un potenziale vettore di exploitation.
Viceversa, l’hardening delle immagini di container, ovvero la selezione di immagini pre-hardened e verificate prima del loro deployment, è in grado di ridurre drasticamente il rischio connesso all’introduzione di software vulnerabile.
L’hardening dei container e delle immagini dei container comporta, innanzitutto, l’analisi del loro stato attuale di sicurezza e, successivamente, l’applicazione delle migliorie in grado di rafforzare iterativamente la sicurezza. Il processo di hardening è ciclico e, pertanto, fornisce anche un monitoraggio iterativo della sicurezza durante l’intero processo di sviluppo. Un’immagine protetta è in grado di fornire all’utente anche una sandbox sicura per lo sviluppo e l’esecuzione dei software.
Processo iterativo di hardening dei container Docker per il rilevamento e la correzione delle vulnerabilità. (Fonte: sei.cmu.edu).
Ipotizziamo un ambiente di sviluppo basato sui container Docker[2]. Il processo di hardening dei container e delle immagini dei container inizia con una semplice scansione della sicurezza dell’immagine non protetta per produrre un elenco delle vulnerabilità note.
Dopo aver realizzato questa baseline, gli sviluppatori devono analizzare il Dockerfile dell’immagine e sostituire qualsiasi immagine di base non standard con immagini di base affidabili provenienti da editor verificati.
Dopo questo “rebasing”, gli sviluppatori dovrebbero iniziare un processo iterativo per correggere eventuali bug noti durante la scansione di nuove vulnerabilità, utilizzando, per esempio, soluzioni di immagini dei container come i Docker.
È fortemente consigliato sostituire le immagini non standard utilizzate in un Dockerfile perché, come accennato in precedenza, le immagini rappresentano il principale veicolo per portare vulnerabilità in grado di insinuarsi nel prodotto finale.
Tutto ciò può assumere la forma di pacchetti vulnerabili o altre dipendenze utilizzate dall’immagine, ma può anche derivare da pratiche non sicure nell’immagine stessa. Per contrastare questo problema, è opportuno riorganizzare il Dockerfile in modo tale da poter utilizzare immagini provenienti da editori verificati, ciò consentirebbe di ottenere una maggiore stabilità durante il deployment degli aggiornamenti e una mitigazione delle vulnerabilità eventualmente presenti.
Questa tattica di scansione e sostituzione delle immagini rappresenta sostanzialmente l’inizio di una rilevazione dei software (SBOM[3]) e, pertanto, semplifica la creazione e il monitoraggio di una SBOM. Il risultato di ogni scansione di sicurezza aiuta a identificare dove sono necessarie le azioni, qualora vengano alla luce nuove vulnerabilità, e può essere aggiunto allo SBOM per mantenerne l’accuratezza.
Riduzione delle potenziali vulnerabilità
È possibile utilizzare altre procedure per mitigare ulteriormente i rischi e le vulnerabilità scoperte dopo il rebasing di un’immagine.
Tanto per cominciare, gli sviluppatori potrebbero implementare, tranne che non lo facciano già, un processo di produzione suddiviso in più fasi.
Ciò consentirebbe al processo di compilazione di installare le dipendenze necessarie per creare i componenti del servizio, lasciando queste dipendenze alla compilazione nelle rispettive fasi. In questo modo il processo di building porterebbe alla fase di creazione finale solo ciò che è necessario per l’immagine finale.
Lasciare queste dipendenze, irrilevanti per il runtime, fuori dall’immagine finale può ridurre la superficie di attacco dell’immagine.
Allo stesso modo, può essere vantaggioso utilizzare immagini di base piccole o leggere. Per esempio, è molto improbabile che la creazione di un container per servire un database necessiti della maggior parte delle funzionalità di un’immagine di base completa del sistema operativo (SO).
In tal senso, la maggior parte delle immagini di base comuni offrono versioni ridotte, tralasciando i componenti e le configurazioni superflue e potenzialmente vulnerabili, e permettono, altresì, di ridurre le dimensioni dell’immagine finale.
Ci sono altre modifiche che possono contribuire a ridurre le potenziali vulnerabilità delle immagini dei container. Una di queste consiste nel sostituire, ove possibile, l’uso dell’istruzione ADD con l’istruzione COPY. In alcuni casi, le funzioni ADD e COPY si sovrappongono in quanto entrambe possono essere usate per spostare i file locali durante il processo di creazione dell’immagine.
Tuttavia, ADD ha la capacità aggiuntiva di scaricare il file con riferimenti a URL esterni e di decomprimere gli archivi. Nel momento in cui la sicurezza rappresenta un problema, queste funzionalità aggiuntive possono essere indesiderate e pericolose.
I container, quando possibile, dovrebbero essere impostati per essere eseguiti come utente di servizio non root, per limitare la loro capacità di eseguire attività dannose nel caso in cui fossero state compromesse. Si noti che per impostazione predefinita, i container vengono eseguiti come root. Eseguendo un contenitore come servizio non root, si segue il principio del minimo privilegio.
È una buona pratica definire l’utente del servizio fin dall’inizio, passando a root nel processo di compilazione solo se necessario.
Altre azioni propedeutiche alla riduzione del rischio possono essere sintetizzate in questi punti:
- Utilizzare immagini provenienti da fonti affidabili: scegliere immagini da registri ufficiali o da fornitori di fiducia.
- Implementare la firma digitale delle immagini: verificare l’integrità e l’autenticità delle immagini utilizzando la firma digitale.
- Mantenere le immagini aggiornate: applicare regolarmente le patch di sicurezza e aggiornare le dipendenze.
- Monitorare l’attività dei container: utilizzare strumenti di monitoraggio per rilevare attività sospette.
- Implementare la sicurezza a livello di runtime: utilizzare soluzioni di sicurezza per proteggere i container durante l’esecuzione.
Seguendo queste best practices, è possibile ridurre significativamente il rischio di esposizione agli attacchi e garantire la sicurezza delle applicazioni containerizzate.
Conclusione
La scansione delle vulnerabilità è un passaggio fondamentale per identificare le vulnerabilità in un’immagine del container. Per esempio, se si utilizzassero strumenti come Grype e Trivy, si potrebbero eseguire scansioni di sicurezza su immagini con regolarità.
La creazione di un processo automatizzato per la scansione ciclica dell’immagine di un container è una parte comune della maggior parte delle pipeline DevSecOps e aggiunge una maggiore visibilità al rilevamento delle vulnerabilità.
La routine di scansione consente, inoltre, di stabilire una baseline delle vulnerabilità e una procedura di mitigazione incrementale.
In genere, le scansioni di vulnerabilità producono un elenco di CVE che contiene informazioni sulle vulnerabilità e sulle eventuali potenziali azioni di mitigazione. Solitamente, un CVE ha un punteggio, detto Common Vulnerability Scoring System (CVSS), che valuta la gravità della vulnerabilità. Il calcolo o la consultazione del punteggio CVSS può semplificare il processo di definizione delle priorità nella risoluzione delle vulnerabilità.
In sintesi, il rafforzamento delle immagini implica:
- l’ispezione del modo in cui viene creata l’immagine;
- la scansione periodica dell’immagine creata per individuare eventuali vulnerabilità;
- l’applicazione di azioni di mitigazione al processo di creazione dell’immagine;
- il monitoraggio delle attività di runtime.
Il processo continuo di scansione e monitoraggio delle immagini aiuta a rilevare eventuali nuove vulnerabilità.
Se si stabilisse un processo di routine per l’hardening delle immagini, si otterrebbe la fiducia nella pipeline di produzione a partire dalla fase di sviluppo.
[1] L’hardening si riferisce al processo di implementazione delle misure di sicurezza in grado di ridurre le vulnerabilità di un sistema. In sostanza, si tratta di rendere un sistema più “duro” da attaccare e, di conseguenza, più “sicuro”.
[2] Docker è una piattaforma per la creazione, la distribuzione e la gestione di applicazioni in container. Un container è un’unità di software che racchiude l’applicazione e tutte le sue dipendenze, isolandola dal sistema operativo e dall’hardware sottostanti. Ciò consente alle applicazioni di essere eseguite in modo affidabile e consistente su qualsiasi ambiente, indipendentemente dalle differenze tra i sistemi operativi, le librerie e le versioni di runtime.
[3] Una “distinta base del software” (SBOM) rappresenta un elemento fondamentale nell’ambito della sicurezza del software e nella gestione del rischio della supply chain del software. Una SBOM è un inventario nidificato, un elenco delle componenti che costituiscono il software.