Gestione delle dipendenze

Questo documento descrive le dipendenze delle applicazioni e le best practice per gestirle, tra cui il monitoraggio delle vulnerabilità, la verifica degli elementi, la riduzione dell'impronta delle dipendenze e il supporto delle 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 del codice, la creazione, l'esecuzione, il download o l'installazione del software.

Le dipendenze possono includere sia i componenti che crei, sia software proprietario di terze parti e software open source. L'approccio che scegli per gestire le 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'elemento e agli strumenti che utilizzi, ma i principi generali rimangono invariati.

Dipendenze dirette e transitive

Le applicazioni possono includere dipendenze sia dirette che transitive:

Dipendenze dirette
Componenti software a cui un'applicazione fa riferimento direttamente.
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 relative relazioni. Inoltre, alcune lingue utilizzano gestori di pacchetti per risolvere l'albero delle dipendenze durante l'installazione o il deployment di un pacchetto.

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

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

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

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

    Una dipendenza transitiva può essere presente in più livelli della 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 la visibilità delle dipendenze indirette, è molto difficile identificare e rispondere a vulnerabilità e altri problemi derivanti 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 versioni scaricate specifiche 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 sicurezza dei tuoi progetti. Questi strumenti forniscono informazioni sui formati dei pacchetti.

Approfondimenti sulla sicurezza nella console Google Cloud
Google Cloud fornisce approfondimenti sulla sicurezza per i tuoi artefatti in Cloud Build, Cloud Run e GKE, tra cui vulnerabilità, informazioni sulle dipendenze, distinta dei materiali di un software (SBOM) e provenienza della compilazione. Anche altri servizi Google Cloud offrono funzionalità che migliorano la tua strategia di sicurezza durante il ciclo di vita dello sviluppo software. Per scoprire di più, consulta la panoramica della sicurezza della catena di fornitura del software.
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 inoltre disponibili questi dati come set di dati Google Cloud. Puoi utilizzare BigQuery per esplorare e analizzare i dati.

  • Database delle vulnerabilità open source: un database delle vulnerabilità sottoposto a ricerca che aggrega le vulnerabilità di altri database in un'unica posizione.

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

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

Approcci per includere le dipendenze

Esistono diversi metodi comuni per includere le dipendenze nella tua applicazione:

Installa direttamente da fonti pubbliche
Installa le dipendenze open source direttamente dai repository pubblici, come Docker Hub, npm, PyPI o Maven Central. Questo approccio è pratico 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.
Memorizzare copie delle dipendenze nel repository di origine
Questo approccio è noto anche come vendoring. Invece di installare una dipendenza esterna da un repository pubblico durante le build, la scarichi e la copi nella struttura ad albero del codice sorgente del progetto. Hai un maggiore controllo sulle dipendenze del fornitore che utilizzi, ma ci sono diversi svantaggi:
  • Le dipendenze del fornitore aumentano le dimensioni del repository di origine e introducono un tasso di abbandono più elevato.
  • Devi specificare le stesse dipendenze in ogni applicazione separata. Se il tuo 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 del fornitore può essere più difficile.
Memorizzare le dipendenze in un registry privato

Un registry privato, come Artifact Registry, offre la comodità di installazione da un repository pubblico, nonché 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 Docker e di lingua in modo che interagiscano 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 dalle origini pubbliche upstream e analizzarle per rilevare le vulnerabilità (anteprima privata).
    • Utilizza i repository virtuali per raggruppare i 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 elemento (anteprima privata).
  • Utilizza Artifact Registry con altri servizi Google Cloud, tra cui Cloud Build, Cloud Run e Google Kubernetes Engine. Utilizza la ricerca automatica di vulnerabilità durante il ciclo di vita dello sviluppo software, genera la provenienza della compilazione, controlla i deployment e visualizza approfondimenti sulla tua postura di sicurezza.

Se possibile, utilizza un registry privato per le dipendenze. Nelle situazioni in cui non puoi utilizzare un registry privato, valuta la possibilità di acquistare le dipendenze in modo da avere il controllo sui contenuti della catena di approvvigionamento del software.

Blocco delle versioni

Il blocco della versione significa limitare una dipendenza dell'applicazione a una versione o a un intervallo di versioni specifico. Idealmente, dovresti bloccare una singola versione di una dipendenza.

L'assegnazione 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 ridurre il problema utilizzando strumenti di gestione delle dipendenze automatiche che monitorano le dipendenze nei repository di codice sorgente per le nuove release. Questi strumenti apportano aggiornamenti ai file dei requisiti per eseguire l'upgrade delle dipendenze, se necessario, spesso includendo informazioni sul log delle modifiche o dettagli aggiuntivi.

Il blocco della versione si applica solo alle dipendenze dirette, non a quelle trasitive. Ad esempio, se blocchi la versione del pacchetto my-library, il blocco limita la versione di my-library, ma non le versioni del software di cui my-library è una dipendenza. Puoi limitare l'albero di 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 elemento 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 elemento con il valore dell'hash calcolato dal fornitore dell'elemento per confermare l'integrità del file. La verifica dell'hash consente di identificare la sostituzione, la manomissione o la corruzione delle dipendenze tramite un attacco man-in-the-middle o la compromissione del repository di elementi.

L'utilizzo della verifica dell'hash richiede di considerare attendibile l'hash che ricevi dal repository degli elementi.

Verifica della firma

La verifica della firma aggiunge ulteriore sicurezza alla procedura di verifica. Il repository degli elementi, i manutentori del software o entrambi possono firmare gli elementi.

Servizi come sigstore offrono ai manutentori un modo per firmare gli elementi software e ai consumatori per verificarne le firme.

Autorizzazione binaria può 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.

Bloccare i file e le 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. Produtti solitamente automaticamente dagli strumenti di installazione, i file di blocco combinano il blocco della versione e la verifica della firma o dell'hash con un albero delle dipendenze completo per l'applicazione.

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

Combinazione di dipendenze private e pubbliche

Le moderne applicazioni cloud-native spesso dipendono sia da codice open source di terze parti sia da librerie interne closed source. Artifact Registry consente di condividere la logica di business su più applicazioni e di riutilizzare gli stessi strumenti per installare librerie sia esterne che interne.

Tuttavia, se combini dipendenze private e pubbliche, la catena di approvvigionamento del software è più vulnerabile a un attacco di confusione delle dipendenze. Se pubblichi progetti con lo stesso nome del tuo progetto interno nei repository open source, gli attaccanti potrebbero essere in grado di sfruttare gli installatori 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 passaggi:

  • Verifica la firma o gli hash delle dipendenze includendole 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 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 Artifact Registry remoti e standard dietro un unico endpoint. Puoi configurare le priorità per i repository a monte in modo che le versioni degli artefatti privati abbiano sempre la priorità rispetto agli artefatti pubblici con lo stesso nome.
  • Utilizza origini 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 dipendenze. Continuare a installare dipendenze inutilizzate con la tua applicazione aumenta il loro impatto e il rischio di compromissione da parte di una vulnerabilità in queste dipendenze.

Una volta che l'applicazione funziona localmente, è buona prassi copiare ogni dipendenza installata durante il processo di sviluppo nel file requirements per l'applicazione. Poi 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.

Fai attenzione quando aggiungi nuove dipendenze all'applicazione. Ognuno di questi elementi ha il potenziale di introdurre altro codice su cui non hai il controllo completo. Nell'ambito della normale pipeline di linting e test, 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 in modo automatico e coerente se le dipendenze stanno introducendo vulnerabilità nella tua applicazione. Gli strumenti di analisi delle vulnerabilità utilizzano i file di blocco per determinare esattamente da quali elementi dipende il tuo progetto e ti avvisano quando vengono rilevate nuove vulnerabilità, a volte anche con percorsi di upgrade suggeriti.

Ad esempio, Artifact Analysis identifica le vulnerabilità del pacchetto del sistema operativo nelle immagini container. Può eseguire la scansione delle immagini quando vengono caricate in Artifact Registry e monitorarle continuamente per trovare nuove vulnerabilità fino a 30 giorni dopo il push dell'immagine.

Puoi anche utilizzare la scansione on demand per eseguire la scansione locale delle immagini container per rilevare vulnerabilità del sistema operativo, di Go e di Java. In questo modo, puoi identificare le vulnerabilità in anticipo per risolverle prima di archiviarle in Artifact Registry.

Passaggi successivi