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'impronta delle dipendenze e il supporto di build riproducibili.

Una dipendenza software è un software necessario per il funzionamento dell'applicazione, ad esempio una libreria software o un plug-in. La risoluzione delle dipendenze può avvenire durante la compilazione, la creazione, l'esecuzione, il download o l'installazione del software.

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

I dettagli per l'implementazione delle best practice possono variare in base al formato dell'artefatto e agli strumenti che utilizzi, ma i principi generali rimangono validi.

Dipendenze dirette e transitive

Le tue applicazioni possono includere dipendenze dirette e transitive:

Dipendenze dirette
Componenti software a cui fa riferimento direttamente un'applicazione.
Dipendenze transitive
Componenti software richiesti funzionalmente dalle dipendenze dirette di un'applicazione. Ogni dipendenza può avere le proprie dipendenze dirette e indirette, creando un albero ricorsivo di dipendenze transitive che influiscono tutte sull'applicazione.

I diversi linguaggi di programmazione offrono diversi livelli di visibilità delle dipendenze e delle loro relazioni. Inoltre, alcune lingue utilizzano i gestori dei pacchetti per risolvere l'albero delle dipendenze durante l'installazione o l'implementazione di un pacchetto.

Nell'ecosistema Node.js, i gestori di pacchetti npm e yarn utilizzano i file di blocco per identificare le versioni delle dipendenze per la creazione di un modulo e le versioni delle dipendenze che un gestore di pacchetti scarica 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 di dipendenze specifici per gestire sistematicamente le dipendenze.

Ad esempio, considera il modulo npm glob versione 8.0.2. Dichiara le dipendenze dirette per i moduli npm nel file package.json. Nel file package.json per glob, la sezione dependencies elenca le dipendenze dirette per il pacchetto pubblicato. La sezione devDepdencies elenca le dipendenze per lo sviluppo e i test locali da parte dei manutentori e dei collaboratori di glob

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

  • Puoi trovare ulteriori informazioni sulle dipendenze di glob sul sito Open Source Insights. L'elenco delle dipendenze per glob include sia le dipendenze dirette sia quelle indirette (transitive).

    Una dipendenza transitiva può trovarsi a più livelli di profondità nella struttura 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 da 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 a vulnerabilità e altri problemi che hanno origine da un componente a cui il tuo codice non fa riferimento direttamente.

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

Strumenti per approfondimenti 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 su tutti i formati dei pacchetti.

Insight sulla sicurezza nella console Google Cloud
Google Cloud fornisce informazioni sulla sicurezza per gli artefatti in Cloud Build, Cloud Run e GKE, incluse vulnerabilità, informazioni sulle dipendenze, distinta base del software (SBOM) e provenienza della build. Anche altri servizi Google Cloud forniscono funzionalità che migliorano la tua security posture durante il ciclo di vita dello sviluppo software. Per scoprire di più, consulta la panoramica di Software Supply Chain Security.
Strumenti open source

Sono disponibili diversi 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 disponibili questi dati anche come Google Cloud set di dati. Puoi utilizzare BigQuery per esplorare e analizzare i dati.

  • Database delle vulnerabilità open source: un database di vulnerabilità in cui vengono aggregate le vulnerabilità di altri database in un'unica posizione.

  • Schede di valutazione: 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 il livello di sicurezza del tuo progetto.

  • Allstar: un'app GitHub che monitora continuamente le organizzazioni o i repository GitHub per verificare il rispetto delle norme configurate. Ad esempio, puoi applicare un criterio alla tua organizzazione GitHub che controlla i collaboratori esterni all'organizzazione che dispongono dell'accesso amministrativo o push.

Approcci per includere le dipendenze

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

Installare direttamente da fonti pubbliche
Installa le dipendenze open source direttamente dai repository pubblici, come Docker Hub, npm, PyPI o Maven Central. Questo approccio è conveniente perché non devi gestire le dipendenze esterne. Tuttavia, poiché non controlli queste dipendenze esterne, la tua catena di fornitura del software è più soggetta ad attacchi alla catena di fornitura open source.
Archiviare copie delle dipendenze nel repository di origine
Questo approccio è noto anche come vendoring. Anziché installare una dipendenza esterna da un repository pubblico durante le build, la scarichi e la copi nell'albero delle origini del progetto. Hai un maggiore controllo sulle dipendenze vendute che utilizzi, ma ci sono diversi svantaggi:
  • Le dipendenze vendute aumentano le dimensioni del repository di origine e introducono più churn.
  • Devi includere le stesse dipendenze in ogni applicazione separata. Se il repository di origine o processo di compilazione non supporta i moduli di origine riutilizzabili, potresti dover gestire più copie delle dipendenze.
  • L'upgrade delle dipendenze vendute può essere più difficile.
Archiviare le dipendenze in un registro privato

Un registro privato, come Artifact Registry, offre la comodità dell'installazione da un repository pubblico, nonché il controllo delle dipendenze. Con Artifact Registry puoi:

  • Centralizza gli artefatti e le dipendenze delle build per tutte le tue applicazioni.
  • Configura i client Docker e dei pacchetti di linguaggio per interagire con i repository privati in Artifact Registry nello stesso modo in cui interagiscono con i repository pubblici.
  • Avere un maggiore controllo sulle dipendenze nei repository privati:

    • Limita l'accesso a ogni repository con Identity and Access Management.
    • Utilizza i repository remoti per memorizzare nella cache le dipendenze da origini pubbliche upstream e analizzarle per rilevare vulnerabilità (anteprima privata).
    • Utilizza i repository virtuali per raggruppare repository remoti e privati dietro un unico endpoint. Imposta una priorità per ogni repository per controllare l'ordine di ricerca durante il download o l'installazione di un artefatto (anteprima privata).
  • Utilizza Artifact Registry con altri servizi, tra cui Cloud Build, Cloud Run e Google Kubernetes Engine. Google Cloud Utilizza la scansione automatica delle vulnerabilità durante il ciclo di vita dello sviluppo software, genera la provenienza della build, controlla i deployment e visualizza approfondimenti 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 vendere le tue dipendenze in modo da avere il controllo sui contenuti della tua catena di fornitura del software.

Blocco delle versioni

Il blocco delle versioni consiste nel limitare una dipendenza dell'applicazione a una versione o a un intervallo di versioni specifico. Idealmente, blocca una singola versione di una dipendenza.

Il blocco della versione di una dipendenza contribuisce a garantire che le build dell'applicazione siano riproducibili. Tuttavia, significa anche che le build non includono aggiornamenti alla dipendenza, incluse correzioni di sicurezza, correzioni di bug o miglioramenti.

Puoi mitigare questo problema utilizzando strumenti di gestione delle dipendenze automatizzati 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 necessità, spesso includendo informazioni sul log delle modifiche o dettagli aggiuntivi.

Il blocco delle versioni si applica solo alle dipendenze dirette, non a quelle transitive. Ad esempio, se blocchi la versione del pacchetto my-library il blocco limita la versione di my-library ma non limita le versioni del software da cui my-library dipende. Puoi limitare l'albero 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 dell'hash

Un hash è un valore generato per un file che funge da 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 dell'hash ti aiuta a identificare la sostituzione, la manomissione o il danneggiamento delle dipendenze, tramite un attacco di tipo man-in-the-middle o una compromissione del repository di artefatti.

L'utilizzo della verifica dell'hash richiede di considerare attendibile l'hash ricevuto dal repository degli artefatti.

Verifica della firma

La verifica della firma aggiunge un ulteriore livello di sicurezza alla procedura di verifica. Gli artefatti possono essere firmati dal repository di artefatti, dai manutentori del software o da entrambi.

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

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

File di blocco e dipendenze compilate

I file di blocco sono file dei requisiti completamente risolti, che specificano esattamente quale versione di ogni dipendenza deve essere installata per un'applicazione. Solitamente prodotti automaticamente dagli strumenti di installazione, i file di blocco combinano il blocco delle versioni e la verifica della firma o dell'hash con un albero delle dipendenze completo per la tua applicazione.

Gli strumenti di installazione creano alberi delle dipendenze risolvendo completamente tutte le dipendenze transitorie downstream delle dipendenze di primo livello e poi includono l'albero delle dipendenze nel file di blocco. Di conseguenza, è possibile installare solo queste dipendenze, 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 closed source. Artifact Registry ti consente di condividere la logica di business in più applicazioni e riutilizzare gli stessi strumenti per installare librerie esterne e interne.

Tuttavia, quando si combinano dipendenze private e pubbliche, la tua supply chain del software è più vulnerabile a un attacco di confusione delle dipendenze. Se pubblichi progetti con lo stesso nome del tuo progetto interno in repository open source, gli autori di attacchi potrebbero 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 adottare una serie di misure:

  • Verifica la firma o gli hash delle tue dipendenze includendoli in un file di blocco.
  • Separa l'installazione delle dipendenze di terze parti e di quelle interne in due passaggi distinti.
  • Esegui il mirroring esplicito delle dipendenze di terze parti di cui hai bisogno 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 upstream.
  • Utilizza i repository virtuali per consolidare i repository remoti e standard di Artifact Registry in un unico endpoint. Puoi configurare le priorità per i repository upstream in modo che le versioni degli artefatti privati abbiano sempre la priorità sugli artefatti pubblici con lo stesso nome.
  • Utilizza fonti attendibili per i pacchetti pubblici e le immagini di base.

Rimozione delle dipendenze inutilizzate

Man mano che le tue esigenze cambiano e la tua applicazione si evolve, potresti modificare o interrompere l'utilizzo di alcune delle tue dipendenze. Se continui a installare dipendenze inutilizzate con la tua applicazione, aumenti l'impronta delle dipendenze e il rischio di essere compromesso da una vulnerabilità in queste dipendenze.

Una volta che l'applicazione funziona localmente, una pratica comune è copiare ogni dipendenza installata durante il processo di sviluppo nel file requirements dell'applicazione. Successivamente, esegui il deployment dell'applicazione con tutte queste dipendenze. Questo approccio contribuisce a garantire il funzionamento dell'applicazione di cui è stato eseguito il deployment, ma è anche probabile che introduca dipendenze non necessarie in produzione.

Presta attenzione quando aggiungi nuove dipendenze alla tua applicazione. Ognuno ha il potenziale di introdurre più codice su cui non hai il controllo completo. Nell'ambito della pipeline di linting e test regolari, integra strumenti che controllano i file dei requisiti per determinare se utilizzi o importi effettivamente le dipendenze.

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

Analisi delle vulnerabilità

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

L'analisi delle vulnerabilità ti consente di valutare automaticamente e in modo coerente se le tue dipendenze introducono vulnerabilità nella tua applicazione. Gli strumenti di scansione delle vulnerabilità utilizzano i file di blocco per determinare esattamente da quali artefatti dipendi e ti avvisano 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ò eseguire la scansione delle immagini quando vengono caricate su Artifact Registry e le monitora continuamente per rilevare nuove vulnerabilità fino a 30 giorni dopo il push dell'immagine.

Puoi anche utilizzare On-Demand Scanning per analizzare localmente le immagini container alla ricerca di vulnerabilità di sistema operativo, Go e Java. In questo modo puoi identificare le vulnerabilità in anticipo per poterle risolvere prima di archiviarle in Artifact Registry.

Passaggi successivi