Gestione delle dipendenze

Questo documento descrive le dipendenze delle applicazioni e le best practice per la loro gestione, tra cui il monitoraggio delle vulnerabilità, la verifica degli artefatti, la riduzione dell'impatto delle dipendenze e il supporto di build riproducibili.

Una dipendenza software è un software che l'applicazione richiede per funzionare, ad esempio una libreria software o un plug-in. La risoluzione delle dipendenze può verificarsi durante la compilazione del codice, la creazione, l'esecuzione, il download o l'installazione del software.

Le dipendenze possono includere sia i componenti creati da te, sia software proprietario di terze parti e software open source. L'approccio adottato per la gestione delle dipendenze può influire sulla sicurezza e sull'affidabilità delle applicazioni.

Le specifiche per l'implementazione delle best practice possono variare in base al formato degli elementi e agli strumenti utilizzati, ma i principi generali si applicano comunque.

Dipendenze dirette e transitive

Le tue applicazioni possono includere dipendenze dirette e transitive:

Dipendenze dirette
Componenti software a cui un'applicazione fa riferimento direttamente.
Dipendenze transitorie
Componenti software richiesti dal punto di vista funzionale delle dipendenze dirette di un'applicazione. Ogni dipendenza può avere le proprie dipendenze dirette e indirette, creando una struttura ricorrente di dipendenze transitive che incidono tutte sull'applicazione.

I diversi linguaggi di programmazione offrono livelli differenti di visibilità sulle dipendenze e sulle loro relazioni. Inoltre, alcuni linguaggi utilizzano i gestori di pacchetti per risolvere la struttura delle dipendenze durante l'installazione o il deployment di un pacchetto.

Nell'ecosistema Node.js, i gestori di pacchetti npm e yarn utilizzano file di blocco per identificare le versioni delle dipendenze per la creazione di un modulo e le versioni delle dipendenze scaricate da un gestore di pacchetti per un'installazione specifica del modulo. In altri ecosistemi linguistici come Java, il supporto per l'introspezione delle dipendenze è più limitato. Inoltre, i sistemi di build devono utilizzare gestori delle dipendenze specifici per gestire sistematicamente le dipendenze.

Considera ad esempio il modulo npm glob versione 8.0.2. Puoi dichiarare le dipendenze dirette per i moduli npm nel file package.json. Nel filepackage.json per glob, la sezione dependencies elenca le dipendenze dirette per il pacchetto pubblicato. La sezione devDepdencies elenca le dipendenze per lo sviluppo locale e i test da parte di gestori e collaboratori di glob

  • Sul sito web npm, la pagina glob elenca le dipendenze dirette e di sviluppo, ma non indica se anche questi moduli hanno le proprie dipendenze.

  • Puoi trovare ulteriori informazioni sulle dipendenze su glob nel sito Open Source Insight. L'elenco delle dipendenze per il glob include sia dipendenze dirette sia dipendenze indirette (transitive).

    Una dipendenza transitiva può essere costituita da più livelli all'interno dell'albero delle dipendenze. Ad esempio:

    1. glob 8.0.2 ha una dipendenza diretta da minimatch 5.0.1.
    2. minimatch 5.0.1 ha una dipendenza diretta brace-expression 2.0.1.
    3. brace-expression 2.0.1 ha una dipendenza diretta da balanced-match 1.0.2.

Senza visibilità sulle dipendenze indirette, è molto difficile identificare e rispondere alle vulnerabilità e ad altri problemi che hanno origine da un componente a cui il codice non fa riferimento direttamente.

Quando installi il pacchetto glob, npm risolve l'intero albero delle dipendenze e salva l'elenco delle specifiche versioni scaricate nel file package.lock.json, in modo da ottenere un record di tutte le dipendenze. Le installazioni successive nello stesso ambiente recupereranno le stesse versioni.

Strumenti per insight sulle dipendenze

Puoi utilizzare i seguenti strumenti per comprendere le dipendenze open source e valutare la postura di sicurezza dei tuoi progetti. Questi strumenti forniscono informazioni in vari formati di pacchetto.

Software Delivery Shield
Una soluzione di sicurezza per la catena di fornitura del software completamente gestita su Google Cloud che consente di visualizzare insight sulla sicurezza per i tuoi artefatti in Cloud Build, Cloud Run e GKE, tra cui vulnerabilità, informazioni sulle dipendenze, fattura del software (SBOM) e provenienza della build. Software Delivery Shield fornisce anche altri servizi e funzionalità per migliorare la tua postura di sicurezza durante tutto il ciclo di vita dello sviluppo del software.
Strumenti open source

Sono disponibili una serie di strumenti open source, tra cui:

  • Open Source Insights: un sito web che fornisce informazioni su dipendenze dirette e indirette note, vulnerabilità note e informazioni sulle licenze per il software open source. Il progetto Open Source Insights rende questi dati disponibili anche come set di dati di Google Cloud. Puoi utilizzare BigQuery per esplorare e analizzare i dati.

  • Database open source di vulnerabilità: un database di vulnerabilità ricercabile che aggrega le vulnerabilità di altri database in un'unica posizione.

  • Prospetti: uno strumento automatizzato che puoi utilizzare per identificare pratiche rischiose della catena di fornitura del software nei tuoi progetti GitHub. Esegue controlli sui repository e assegna a ogni controllo un punteggio da 0 a 10. Puoi quindi utilizzare i punteggi per valutare la postura di sicurezza del progetto.

  • Allstar: un'app GitHub che monitora costantemente le organizzazioni o i repository GitHub per verificare la conformità ai criteri configurati. Ad esempio, puoi applicare un criterio alla tua organizzazione GitHub per verificare la presenza di collaboratori esterni all'organizzazione con accesso amministrativo o push.

Approcci all'inclusione delle dipendenze

Esistono diversi metodi comuni per includere le dipendenze nell'applicazione:

Installa direttamente da origini pubbliche
Installa dipendenze open source direttamente dai repository pubblici, come Docker Hub, npm, PyPI o Maven Central. Questo approccio è pratico perché non devi mantenere le dipendenze esterne. Tuttavia, poiché non hai il controllo di queste dipendenze esterne, la tua catena di fornitura del software è più soggetta ad attacchi open source.
Archivia copie delle dipendenze nel repository di codice sorgente
Questo approccio è anche noto come vendoring. Invece di installare una dipendenza esterna da un repository pubblico durante le build, puoi scaricarla e copiarla nella struttura ad albero di origine del progetto. Hai maggiore controllo sulle dipendenze utilizzate dal fornitore, ma esistono diversi svantaggi:
  • Le dipendenze fornite da fornitori aumentano le dimensioni del repository di codice sorgente e aumentano il tasso di abbandono.
  • Devi fornire le stesse dipendenze in ogni applicazione separata. Se il repository di codice sorgente o il processo di compilazione non supporta i moduli di origine riutilizzabili, potrebbe essere necessario mantenere più copie delle dipendenze.
  • L'upgrade delle dipendenze del fornitore può essere più difficile.
Archivia le dipendenze in un registro privato
Un registry privato, come Artifact Registry, offre la comodità di installazione da un repository pubblico e il controllo sulle dipendenze. Con Artifact Registry, puoi:
  • Centralizza gli artefatti e le dipendenze della build per tutte le tue applicazioni.
  • Configura i client dei pacchetti di linguaggio e Docker per interagire con i repository privati in Artifact Registry come fanno con i repository pubblici.
  • Avere un maggiore controllo sulle tue dipendenze nei repository privati:
  • Limitare l'accesso a ogni repository con Identity and Access Management.
  • Utilizza repository remoti per memorizzare nella cache le dipendenze da origini pubbliche upstream e analizzarle per rilevare eventuali vulnerabilità (anteprima privata).
  • Utilizza repository virtuali per raggruppare repository remoti e privati dietro un singolo endpoint. Imposta una priorità su ogni repository per controllare l'ordine di ricerca durante il download o l'installazione di un artefatto (anteprima privata).
  • Utilizza facilmente Artifact Registry con altri servizi Google Cloud in Software Delivery Shield, tra cui Cloud Build, Cloud Run e Google Kubernetes Engine. Utilizza l'analisi automatica delle vulnerabilità durante il ciclo di vita di sviluppo del software, genera la provenienza delle build, controlla i deployment e visualizza insight sulla tua postura di sicurezza.

Se possibile, utilizza un registro privato per le dipendenze. Nelle situazioni in cui non puoi utilizzare un registro privato, valuta la possibilità di fornire le dipendenze tramite il fornitore delle dipendenze, in modo da avere il controllo sui contenuti della catena di fornitura del software.

Blocco delle versioni

Bloccare la versione significa limitare la dipendenza di un'applicazione a una versione o a un intervallo di versioni specifico. Idealmente, devi bloccare una singola versione di una dipendenza.

Il blocco della versione di una dipendenza aiuta a garantire che le build delle applicazioni siano riproducibili. Tuttavia, ciò significa anche che le tue build non includono aggiornamenti della dipendenza, tra cui correzioni di sicurezza, correzioni di bug o miglioramenti.

Puoi mitigare questo problema utilizzando strumenti di gestione automatizzata delle dipendenze che monitorano le dipendenze nei repository di origine per le nuove release. Questi strumenti aggiornano i file dei requisiti per eseguire l'upgrade delle dipendenze in base alle esigenze, spesso includono informazioni del log delle modifiche o dettagli aggiuntivi.

Il blocco della versione si applica solo alle dipendenze dirette, non a quelle transitive. Ad esempio, se blocchi la versione del pacchetto my-library, il PIN limita la versione di my-library, ma non le versioni del software per cui my-library ha una dipendenza. Puoi limitare la struttura delle dipendenze per un pacchetto in alcune lingue utilizzando un file di blocco.

Verifica della firma e dell'hash

Esistono diversi metodi che puoi utilizzare per verificare l'autenticità di un artefatto che utilizzi come dipendenza.

Verifica hash

Un hash è un valore generato per un file che agisce come identificatore univoco. Puoi confrontare l'hash di un artefatto con il valore hash calcolato dal provider dell'artefatto per confermare l'integrità del file. La verifica tramite hash ti aiuta a identificare la sostituzione, le manomissioni o il danneggiamento delle dipendenze tramite un attacco man in the middle o una compromissione del repository degli artefatti.

L'utilizzo della verifica degli hash richiede la certezza che l'hash che ricevi dal repository degli artefatti non sia compromesso.

Verifica della firma

La verifica della firma aggiunge ulteriore sicurezza al processo di verifica. Il repository di artefatti, i gestori del software o entrambi possono firmare gli artefatti.

Servizi come sigstore consentono ai gestori di firmare gli artefatti software e ai consumatori di verificare queste firme.

Autorizzazione binaria consente di verificare che le immagini container di cui è stato eseguito il deployment negli ambienti di runtime di Google Cloud siano firmate con attestazioni per una serie di criteri.

Blocca file e dipendenze compilate

I file di blocco sono file dei requisiti completamente risolti che specificano esattamente la versione di ogni dipendenza da installare per un'applicazione. Solitamente prodotti automaticamente dagli strumenti di installazione, i file di blocco combinano il blocco della versione e la verifica della firma o dell'hash con una struttura ad albero delle dipendenze completa per la tua applicazione.

Gli strumenti di installazione creano alberi delle dipendenze risolvendo completamente tutte le dipendenze transitorie a valle delle dipendenze di primo livello, quindi includono la struttura delle dipendenze nel file di blocco. Di conseguenza, solo queste dipendenze possono essere installate, rendendo le build più riproducibili e coerenti.

Combinazione di dipendenze private e pubbliche

Le moderne applicazioni cloud-native spesso dipendono da codice open source e di terze parti, nonché da librerie interne open source. Artifact Registry ti consente di condividere la tua logica di business tra più applicazioni e riutilizzare gli stessi strumenti per installare librerie esterne e interne.

Tuttavia, quando si combinano dipendenze private e pubbliche, la catena di fornitura del software è più vulnerabile a un attacco di confusione delle dipendenze. Pubblicando progetti con lo stesso nome del tuo progetto interno in repository open source, gli utenti malintenzionati potrebbero essere in grado di sfruttare programmi di installazione configurati in modo errato per installare il loro codice dannoso anziché la tua dipendenza interna.

Per evitare un attacco di confusione delle dipendenze, puoi seguire diversi passaggi:

  • Verifica la firma o gli hash delle tue dipendenze includendoli in un file di blocco.
  • Separa l'installazione delle dipendenze di terze parti e delle dipendenze interne in due passaggi distinti.
  • Esegui il mirroring esplicito delle dipendenze di terze parti necessarie nel tuo repository privato, manualmente o con un proxy pull-through. I repository remoti di Artifact Registry sono proxy pull-through per i repository pubblici a monte.
  • Utilizza i repository virtuali per consolidare i repository Artifact Registry remoti e standard dietro un singolo endpoint. Puoi configurare le priorità per i repository upstream in modo che le versioni private degli artefatti abbiano sempre la priorità sugli artefatti pubblici con lo stesso nome.
  • Utilizza origini attendibili per pacchetti pubblici e immagini di base.

Rimozione delle dipendenze inutilizzate

Man mano che le tue esigenze cambiano e la tua applicazione si evolve, potresti cambiare o smettere di utilizzare alcune delle tue dipendenze. Continuare a installare le dipendenze inutilizzate con la tua applicazione aumenta l'impatto delle dipendenze e il rischio di compromissione tramite vulnerabilità in queste dipendenze.

Una volta che l'applicazione funziona in locale, una prassi comune consiste nel copiare nel file dei requisiti dell'applicazione ogni dipendenza installata durante il processo di sviluppo. Quindi, esegui il deployment dell'applicazione con tutte queste dipendenze. Questo approccio aiuta a garantire che l'applicazione di cui è stato eseguito il deployment funzioni, ma è anche probabile che introduca dipendenze non necessarie in produzione.

Fai attenzione quando aggiungi nuove dipendenze all'applicazione. Ognuno ha il potenziale di introdurre più codice di cui non hai il controllo completo. Nell'ambito della normale pipeline di analisi tramite lint e test, integra gli strumenti che verificano i file dei requisiti per determinare se le dipendenze vengono effettivamente utilizzate o importate.

Alcuni linguaggi dispongono di strumenti per aiutarti a gestire le dipendenze. Ad esempio, puoi utilizzare il plug-in di dipendenze Maven per analizzare e gestire le dipendenze Java.

Analisi delle vulnerabilità

Rispondere rapidamente alle vulnerabilità nelle tue dipendenze ti aiuta a proteggere la catena di fornitura del software.

L'analisi delle vulnerabilità consente di valutare in modo automatico e coerente se le dipendenze stanno introducendo vulnerabilità nella propria applicazione. Gli strumenti di analisi delle vulnerabilità consumano file di blocco per determinare esattamente da quali artefatti dipendono e ti inviano una notifica quando emergono nuove vulnerabilità, a volte anche con percorsi di upgrade suggeriti.

Ad esempio, Artifact Analysis identifica le vulnerabilità dei pacchetti del sistema operativo nelle immagini container. Può analizzare le immagini quando vengono caricate su Artifact Registry e monitorarle continuamente per trovare nuove vulnerabilità per un massimo di 30 giorni dopo il push dell'immagine.

Puoi anche utilizzare l'analisi on demand per scansionare le immagini container localmente alla ricerca di vulnerabilità del sistema operativo OS, Go e Java. In questo modo puoi identificare in anticipo le vulnerabilità per risolverle prima di archiviarle in Artifact Registry.

Passaggi successivi