Gestione sicura dei segreti in GitHub Actions

Ultimo aggiornamento: 12/01/2025
  • I segreti di GitHub Actions sono variabili di ambiente crittografate e con ambito definito, che devono essere attentamente definite a livello di repository, ambiente e organizzazione.
  • La sicurezza si basa sul minimo privilegio, evitando l'esposizione dei log, ruotando e verificando i segreti e isolando gli ambienti di produzione sensibili.
  • I rischi derivanti dall'iniezione di script, dalle azioni di terze parti e dai runner auto-ospitati richiedono il pinning, la revisione del codice e rigide politiche sui runner e sulle autorizzazioni.
  • OpenID Connect e i gestori di segreti esterni aiutano a sostituire le credenziali di lunga durata con token di breve durata e flussi di lavoro segreti centralizzati e verificabili.

Gestione dei segreti di GitHub Actions

La gestione dei segreti in GitHub Actions è uno di quegli argomenti che a prima vista sembrano semplici, ma che si trasformano rapidamente in un problema di sicurezza critico quando le pipeline iniziano a entrare in contatto con la produzione, i provider cloud e i servizi di terze parti. I flussi di lavoro CI/CD gestiscono regolarmente chiavi API, password di database, chiavi SSH, token e altro ancora, e ciascuno di questi valori rappresenta un potenziale punto di ingresso per un aggressore se gestito con noncuranza.

In questa guida approfondiremo il funzionamento dei segreti in GitHub Actions, come configurarli a livello di repository, ambiente e organizzazione, come rafforzare i flussi di lavoro contro perdite e attacchi alla supply chain e quando ha senso coinvolgere gestori di segreti esterni. L'idea è di fornirti una panoramica pratica e incentrata sulla sicurezza, in modo da poter mantenere veloci i tuoi pipeline e al sicuro senza trasformare il lavoro quotidiano in un grattacapo.

Cosa sono esattamente i segreti di GitHub Actions?

In GitHub Actions, un "segreto" è una variabile di ambiente crittografata il cui valore è nascosto all'interfaccia utente, ai registri e ai contenuti del repository. Lo definisci una volta (a livello di repository, organizzazione o ambiente) e poi lo fai riferimento nel tuo flusso di lavoro YAML utilizzando secrets. contesto, in modo che le pipeline possano utilizzare valori sensibili senza mai inserirli nella base di codice.

Dietro le quinte, GitHub crittografa i segreti utilizzando una crittografia avanzata (caselle sigillate Libsodium) prima ancora che raggiungano i server di GitHub, e i valori vengono decrittografati solo in fase di esecuzione sul workflow runner. Una volta creati, i segreti sono immutabili dall'interfaccia utente: puoi sovrascriverli ma non puoi leggerli di nuovo e quando appaiono nei registri vengono automaticamente mascherati con *** ove possibile.

Questo modello presenta alcuni importanti vincoli di progettazione di cui è necessario essere a conoscenza: i segreti non possono essere recuperati tramite l'interfaccia utente o l'API, vengono eliminati dai registri e risiedono in un ambito specifico: repository, ambiente all'interno di un repository o organizzazione. Scegliere l'ambito giusto è la prima grande decisione per una strategia di riservatezza sensata.

Ambiti segreti in GitHub Actions

Segreti di repository, ambiente e organizzazione

GitHub offre tre livelli principali per i segreti: segreti del repository, segreti dell'ambiente e segreti dell'organizzazione, ognuno con i propri casi d'uso e regole di precedenza. Comprendere come interagiscono aiuta a evitare conflitti e a mantenere i valori sensibili al loro posto.

Segreti a livello di repository

I segreti del repository sono collegati a un singolo repository e sono disponibili per tutti i flussi di lavoro in quel repository. Sono perfetti per valori specifici del progetto, come la chiave API di un servizio, una password di distribuzione o un token webhook utilizzato solo da quel repository.

Per creare un segreto del repository dall'interfaccia utente, vai al repository di destinazione, apri "Impostazioni" → "Segreti e variabili" → "Azioni", quindi fai clic su "Nuovo segreto del repository". Gli assegni un nome in maiuscolo con caratteri di sottolineatura (ad esempio PAYMENTS_API_KEY), incolla il valore segreto e salva; da quel momento in poi, i flussi di lavoro possono accedervi come ${{ secrets.PAYMENTS_API_KEY }}.

Chiunque abbia accesso in scrittura al repository può fare riferimento a questi segreti nei flussi di lavoro, quindi le autorizzazioni sul repository stesso diventano parte della tua storia di sicurezza. Se concedi casualmente l'accesso in scrittura a molti utenti, stai implicitamente concedendo loro l'accesso per utilizzare ogni segreto del repository nell'automazione.

Segreti specifici dell'ambiente

I segreti dell'ambiente si trovano un livello sotto i segreti del repository e consentono di definire valori diversi per ambiente, ad esempio dev, staging, o production. Sono collegati a un ambiente denominato e possono essere protetti con regole quali revisori obbligatori o timer di attesa prima che un processo possa essere eseguito su di essi.

Per configurarli, vai su "Impostazioni" → "Ambienti", crea o seleziona un ambiente e poi aggiungi i segreti all'interno della configurazione di quell'ambiente. I nomi segreti usano ancora il secrets. contesto (ad esempio secrets.PROD_DB_PASSWORD), ma i valori diventano disponibili solo per i processi che vengono eseguiti esplicitamente in quell'ambiente.

Un dettaglio fondamentale è che i segreti dell'ambiente hanno la precedenza sui segreti del repository quando condividono lo stesso nome. Ciò significa che puoi definire DB_PASSWORD a livello di repository per usi locali/di prova e quindi avere un diverso DB_PASSWORD come segreto di ambiente per la produzione che ha la precedenza nei lavori di produzione, senza modificare la sintassi del flusso di lavoro.

Gli ambienti consentono anche regole di protezione come "revisori obbligatori" o "solo da determinati rami", il che è incredibilmente utile per creare delle barriere di protezione attorno all'accesso ai tuoi segreti più sensibili. Ad esempio, un ambiente di produzione potrebbe richiedere l'approvazione di DevOps prima che qualsiasi processo che utilizzi i suoi segreti possa essere eseguito.

Segreti a livello di organizzazione

I segreti dell'organizzazione vengono condivisi tra più repository in un'organizzazione e sono ideali per credenziali ampiamente riutilizzate, come un webhook Slack condiviso o un token API di metriche centrali. Riducono la duplicazione e semplificano la rotazione perché si aggiorna il segreto una volta sola e tutti i repository che lo utilizzano acquisiscono il nuovo valore.

Gli amministratori li creano nella sezione "Impostazioni" → "Segreti e variabili" → "Azioni" dell'organizzazione, cliccando su "Nuovo segreto dell'organizzazione" e quindi scegliendo quali repository possono accedere a quel segreto. È possibile consentire tutti i repository attuali e futuri oppure limitarli rigorosamente a un sottoinsieme specifico.

C'è una catena di precedenza che dovresti tenere a mente: segreto dell'organizzazione < segreto del repository < segreto dell'ambiente quando i nomi entrano in conflitto. In altre parole, un segreto ambientale prevale su un segreto di repository, che a sua volta prevale su un segreto di organizzazione se tutti condividono la stessa chiave.

Come si comportano i segreti in fase di esecuzione

Una volta definiti, i segreti diventano disponibili per i lavori in fase di esecuzione tramite secrets contesto e vengono iniettati come variabili di ambiente quando vengono referenziati. Non vengono esportati in modo esteso in ogni fase per impostazione predefinita; li colleghi esplicitamente nel tuo env: blocchi o passarli ad azioni che supportano i segreti come input.

GitHub fornisce anche uno speciale GITHUB_TOKEN per esecuzione del flusso di lavoro, che non è un segreto definito manualmente ma si comporta come tale e viene spesso utilizzato per chiamate API o operazioni di repository. È possibile (e si dovrebbe) regolare i permessi dettagliati di questo token utilizzando permissions: bloccare in modo che abbia l'ambito minimo necessario per ogni lavoro.

Per ridurre l'esposizione accidentale, GitHub maschera qualsiasi valore che corrisponde a un segreto registrato nei registri del flusso di lavoro, sostituendolo con ***. Questo mascheramento viene eseguito sul lato runner e generalmente funziona bene per le stringhe non elaborate, ma presuppone che il valore esatto del segreto appaia nell'output. Se si trasforma il segreto (ad esempio, codificandolo in base64 o incorporandolo in un file JSON strutturato), la maschera potrebbe non riuscire a rilevarlo.

Poiché il mascheramento è il metodo più efficace e non è matematicamente garantito, è opportuno progettare flussi di lavoro in modo da evitare del tutto la stampa di segreti o dei loro derivati ​​nei registri e utilizzare comandi di mascheramento per i valori aggiuntivi generati in fase di esecuzione. Considera i registri come potenzialmente visibili a più persone di quanto pensi e dai per scontato che qualsiasi cosa venga stampata potrebbe trapelare.

Utilizzo pratico: riferimento ai segreti nei flussi di lavoro

Nella maggior parte dei casi, i segreti vengono utilizzati mappandoli in variabili di ambiente in un passaggio o in un processo specifico e quindi consentendo agli script o agli strumenti di leggere dall'ambiente. Uno schema classico si presenta così:


– nome: Distribuisci su API
ambiente:
API_KEY: ${{ secrets.PROD_API_KEY }}
correre: |
curl -H "Autorizzazione: Bearer $API_KEY" https://api.example.com/deploy

È anche possibile scrivere un segreto in un file sul runner, che è sicuro finché il file rimane all'interno dell'area di lavoro temporanea del processo e non viene eseguito il commit o caricato come artefatto. Ad esempio, per memorizzare una chiave SSH:


– nome: Scrivi la chiave SSH nel file
shell: bash
ambiente:
SSH_KEY: ${{ segreti.SSH_KEY }}
correre: |
echo “$SSH_KEY” > chiave
chiave chmod 600

Dai registri vedrai solo il comando shell effettivo (con $SSH_KEY), ma non il valore segreto stesso, che verrà redatto o nascosto. Poiché i runner ospitati su GitHub vengono distrutti al termine del processo, il file temporaneo scompare con la VM; nei runner auto-ospitati è necessario essere molto più rigorosi nella pulizia.

Best practice di sicurezza per i segreti in GitHub Actions

Utilizzare solo l'interfaccia utente segreta non è sufficiente; è necessario un insieme di abitudini e misure di sicurezza per ridurre al minimo il raggio dell'esplosione se qualcosa va storto. GitHub mette a disposizione numerose manopole, ma sta a te ruotarle correttamente.

Applicare il principio del privilegio minimo

Ogni segreto e ogni token dovrebbero concedere solo i permessi assolutamente necessari per una determinata attività. Per i servizi esterni, creare credenziali dedicate con autorizzazioni definite (ad esempio "solo distribuzione" o "metriche di sola lettura") anziché riutilizzare chiavi di amministrazione complete.

Lo stesso principio si applica al built-in GITHUB_TOKEN; imposta i permessi predefiniti al minimo indispensabile (spesso contents: read) e quindi aumentare i permessi solo in lavori specifici che ne necessitano di più. Puoi configurarlo con un permissions: sezione a livello di flusso di lavoro o di processo in modo che un processo compromesso non possa eseguire silenziosamente scritture arbitrarie.

Evitare di stampare o codificare i segreti nei registri

I segreti non dovrebbero mai essere codificati in modo rigido nei file del flusso di lavoro o stampati in testo normale per comodità di debug. Se hai altri valori sensibili che non sono registrati come segreti GitHub (ad esempio un token generato in fase di esecuzione), puoi comunque chiedere al runner di trattarli come segreti utilizzando la sintassi del comando:


echo “::add-mask::$GENERATED_TOKEN”

I blob strutturati come JSON, XML o i grandi documenti YAML sono particolarmente pericolosi come segreti perché il masker di GitHub si basa sulla corrispondenza esatta delle stringhe. Se si inseriscono più valori sensibili all'interno di una grande stringa JSON e la si utilizza come un unico segreto, piccole modifiche alla formattazione possono causare il fallimento del mascheramento; in alternativa, è opportuno definire segreti individuali per ogni campo delicato.

Quando si testano i flussi di lavoro, controllare sempre i registri, prestando particolare attenzione ai messaggi di errore e alle tracce dello stack. Alcuni strumenti ripetono volentieri comandi e flag su stderr, il che può includere accidentalmente valori segreti, a meno che non si eviti esplicitamente tale schema.

Ruotare e controllare regolarmente i segreti

La rotazione dei segreti è noiosa ma non negoziabile se si ha a cuore la sicurezza; lasciare le credenziali invariate per anni aumenta la finestra di opportunità per gli aggressori. Una base ragionevole è quella di ruotare i segreti di produzione più critici ogni mese, quelli ad alto rischio ogni due mesi e tutto il resto almeno ogni trimestre.

È possibile automatizzare parte di questa operazione utilizzando l'API REST di GitHub per i segreti, che consente di recuperare la chiave pubblica di un repository o di un'organizzazione e caricare nuovi valori crittografati. Questa funzionalità è particolarmente utile per le grandi organizzazioni con molti repository che condividono account di servizio e devono ruotarli rapidamente in risposta agli incidenti.

L'audit è altrettanto importante: rivedere periodicamente l'elenco dei segreti configurati ed eliminare quelli che non vengono più utilizzati e utilizzare i registri di sicurezza/audit di GitHub per tenere traccia di eventi come org.update_actions_secret. In questo modo saprai chi ha modificato cosa e quando, e potrai correlare i cambiamenti sospetti con altre attività.

Utilizzare la separazione basata sull'ambiente

I segreti ambientali rappresentano uno dei modi più semplici per rafforzare le pipeline, perché consentono di isolare valori altamente sensibili (come le credenziali del database di produzione) dietro approvazioni esplicite. È possibile richiedere revisori umani, limitare le filiali che possono effettuare la distribuzione e persino aggiungere timer di pausa prima dell'inizio di una distribuzione.

Uno schema comune è quello di avere un staging ambiente con protezioni blande e un production ambiente con regole più severe e segreti separati. I flussi di lavoro definiscono quindi i lavori destinati a ciascun ambiente, garantendo che i segreti di produzione non vengano mai utilizzati accidentalmente nei lavori di test.

Scegli convenzioni di denominazione chiare

Una buona denominazione per i segreti ti evita frustranti congetture e pericolosi errori. Invece di nomi generici come API_KEY, codificare il servizio e l'ambiente nel nome, ad esempio STRIPE_PROD_API_KEY or AWS_STAGING_DEPLOY_ROLE_ARN.

I team che si occupano di molti servizi spesso adottano uno schema come <SERVICE>_<ENV>_<PURPOSE>. Quindi potresti avere SLACK_PROD_ALERTS_WEBHOOK, GCP_DEV_BUILD_SERVICE_ACCOUNTe DB_STAGING_PASSWORDIn questo modo diventa molto più ovvio quale segreto debba essere utilizzato in quale lavoro.

Protezione contro l'iniezione di script e rischi di terze parti

I segreti non sono solo a rischio a causa di una configurazione errata, ma sono anche bersagli allettanti per l'iniezione di script nei flussi di lavoro e per azioni di terze parti dannose o compromesse. Se non si presta attenzione, un singolo passaggio vulnerabile può far trapelare tutti i segreti accessibili al lavoro.

Mitigazione dell'iniezione di script nei passaggi in linea

Quando si interpolano dati non attendibili (come titoli di pull request, nomi di branch o commenti di issue) direttamente negli script shell, si apre la porta all'iniezione. Ad esempio, un titolo PR potrebbe essere creato per uscire da un comando ed eseguire codice shell arbitrario nel tuo runner.

L'approccio più sicuro è quello di gestire la logica complessa in azioni JavaScript/TypeScript proprietarie o ben verificate e passare valori non attendibili come input anziché incorporarli nella shell. In questo modello, l'azione riceve stringhe come argomenti e le elabora senza generare script shell che possano essere dirottati.

Se devi utilizzare la shell inline, memorizza prima i valori non attendibili nelle variabili di ambiente e poi fai riferimento a tali variabili, preferibilmente tra virgolette doppie. In questo modo il valore viene trattato come dato anziché come parte della struttura dello script, riducendo notevolmente le probabilità di successo dei tentativi di iniezione.

Aggiungi e rivedi le azioni di terze parti

Ogni azione di terze parti che inserisci nel tuo flusso di lavoro viene eseguita con accesso all'ambiente e ai segreti del processo, quindi dovresti trattarle come dipendenze del codice che richiedono un esame approfondito. Un'azione dannosa o compromessa può leggere i segreti e inviarli a un aggressore con una singola chiamata HTTP.

La procedura migliore è quella di bloccare le azioni tramite SHA full commit anziché solo tag o branch, perché i tag possono essere spostati o sovrascritti. Un SHA si riferisce a una versione esatta del codice, rendendo molto più difficile per un aggressore iniettare silenziosamente un nuovo comportamento senza che venga aggiornato il flusso di lavoro.

Prima di utilizzare un'azione, esamina attentamente il suo codice sorgente (o almeno esegui una revisione della sicurezza) per assicurarti che gestisca i segreti in modo responsabile e non li registri o li invii a endpoint sconosciuti. Se utilizzi azioni di marketplace, verifica l'editore ove possibile e affidati a Dependabot per ricevere avvisi su vulnerabilità e aggiornamenti.

Corridori ospitati vs. auto-ospitati e visibilità segreta

Il luogo in cui vengono eseguiti i flussi di lavoro ha un impatto enorme sulla sicurezza con cui vengono gestiti i segreti. I runner ospitati su GitHub e quelli auto-ospitati si comportano in modo molto diverso in termini di isolamento e persistenza.

I runner ospitati su GitHub avviano nuove macchine virtuali per ogni lavoro, eseguono il flusso di lavoro e poi le smantellano. In questo modo si ottiene sempre un ambiente pulito e si garantisce che tutti i file o le variabili di ambiente (inclusi i segreti scritti sul disco) vengano distrutti una volta completato il lavoro.

Al contrario, i runner self-hosted sono macchine longeve gestite dall'utente, il che significa che qualsiasi codice con accesso ai segreti può potenzialmente persistere o esfiltrarli oltre la durata di un singolo processo. Nei repository pubblici, i runner auto-ospitati sono particolarmente rischiosi perché i contributori non attendibili possono aprire richieste pull che attivano flussi di lavoro.

Se si utilizzano runner self-hosted, è opportuno isolarli in base al livello di sensibilità, limitare quali repository possono utilizzare quali runner e prestare attenzione a ciò che si trova su tali macchine (chiavi SSH, credenziali cloud, accesso di rete ai servizi interni e così via). Alcune organizzazioni utilizzano runner auto-ospitati "just-in-time" (JIT), creati tramite API per un singolo processo e poi eliminati, ma anche in questo caso è necessario assicurarsi che i processi non condividano inaspettatamente lo stesso runner.

Utilizzo di OpenID Connect (OIDC) al posto di segreti cloud di lunga durata

Uno dei maggiori vantaggi dell'igiene segreta in GitHub Actions è la sostituzione delle chiavi di accesso al cloud di lunga durata con credenziali di breve durata tramite OpenID Connect. Invece di archiviare le chiavi AWS, Azure o GCP come segreti, i flussi di lavoro richiedono token temporanei al provider cloud utilizzando GitHub come provider di identità.

Il flusso è più o meno questo: il job richiede un JWT firmato dall'endpoint OIDC di GitHub, il provider cloud convalida tale token e lo scambia con credenziali di breve durata, e il flusso di lavoro utilizza tali credenziali per tutta la durata del job. Nessun segreto statico deve necessariamente risiedere su GitHub.

Ad esempio, con AWS si configura un ruolo IAM che si fida del provider GitHub OIDC e limita i repository/rami che possono assumere tale ruolo. Quindi nel tuo flusso di lavoro usi un'azione come aws-actions/configure-aws-credentials con autorizzazioni OIDC abilitate per ottenere credenziali al volo.

Questo approccio presenta molteplici vantaggi: non c'è nulla da ruotare all'interno di GitHub, i token hanno automaticamente una durata breve, l'accesso è limitato e si ottengono registri di controllo completi sul lato cloud che tengono traccia di ogni assunzione di ruolo. Per gli ambienti ad alta sicurezza, OIDC dovrebbe essere l'impostazione predefinita, ove supportato.

Strumenti nativi e gestori segreti esterni

I segreti integrati di GitHub sono ideali per molti scenari, ma a un certo punto potresti aver bisogno di un gestore dei segreti più centralizzato e ricco di funzionalità, che si estenda a più piattaforme e ambienti. A questo scopo vengono spesso utilizzati strumenti come HashiCorp Vault, Infisical o Doppler insieme a GitHub Actions.

Questi sistemi possono gestire segreti dinamici (ad esempio, generando utenti di database di breve durata), policy di controllo degli accessi avanzate, registri di controllo dettagliati e flussi di lavoro di rotazione che vanno oltre ciò che offre solo GitHub. Le GitHub Actions si autenticano quindi presso questi gestori (spesso tramite OIDC o un altro metodo di autenticazione), recuperano i segreti di cui hanno bisogno in fase di esecuzione e li utilizzano senza mai memorizzare credenziali a lungo termine nel repository.

Esistono anche azioni e plugin della community progettati per estrarre i segreti dai manager esterni direttamente nei flussi di lavoro. Quando li si utilizza, si applicano gli stessi consigli: rivedere la fonte dell'azione, associarla a un commit SHA e limitare i privilegi concessi al token o al ruolo che utilizza per raggiungere il sistema esterno.

La gestione sicura dei segreti in GitHub Actions significa scegliere l'ambito giusto per ogni segreto, applicare il privilegio minimo, utilizzare ambienti e OIDC dove appropriato, trattare i log e le azioni di terze parti come potenziali superfici di attacco e affidarsi a gestori di segreti esterni quando i requisiti di scalabilità o conformità lo richiedono. Con queste pratiche in atto, le pipeline CI/CD possono rimanere flessibili e veloci, riducendo drasticamente le possibilità che un token fuori posto o un flusso di lavoro mal revisionato si trasformino in un incidente conclamato.

Related posts: