Qualsiasi utente con accesso in scrittura a un repository può leggere tutti i secret configurati al suo interno, quindi le credenziali usate dai workflow dovrebbero avere il minimo privilegio che il job consente. La guida di GitHub all'uso sicuro dice lo stesso a proposito del GITHUB_TOKEN: gli si assegnino i permessi minimi necessari. Le cinque attività che seguono richiedono un giorno ciascuna.
La OWASP Top 10 CI/CD Security Risks indica le aree critiche toccate da questo piano: gestione inadeguata di identità e accessi (CICD-SEC-2), abuso della catena di dipendenze (CICD-SEC-3), esecuzione di pipeline avvelenate (CICD-SEC-4), scarsa igiene delle credenziali (CICD-SEC-6) e validazione impropria dell'integrità degli artefatti (CICD-SEC-9).
Lunedì: ridurre ciò che il token può fare
Si parta dal presupposto che chiunque possa fare push sul repository possa leggerne i secret. Poi si riduca ciò che quelle credenziali possono raggiungere.
GitHub raccomanda di impostare il permesso predefinito del GITHUB_TOKEN in sola lettura per i contenuti del repository, e di elevarlo solo per i singoli job. In un file di workflow:
permissions:
contents: read
jobs:
release:
permissions:
contents: write
Esamini ogni workflow e si chieda quali job scrivano davvero qualcosa. Assegni l'accesso in scrittura a quei job e lasci tutti gli altri in sola lettura.
Martedì: fare l'audit delle action in uso
Elenchi tutte le action usate nei suoi workflow, comprese quelle richiamate da reusable workflow che gestisce direttamente. Per ciascuna, legga il codice sorgente dell'action e del repository che la esegue.
Le indicazioni di GitHub sull'audit dei secret sono precise su cosa cercare. Verifichi che i secret non vengano inviati a host non previsti e che non vengano stampati esplicitamente nei log di output.
Applichi poi il minimo privilegio alle credenziali stesse. Poiché l'accesso in scrittura al repository equivale all'accesso in lettura ai suoi secret, ogni credenziale usata da un workflow dovrebbe poter fare soltanto ciò che a quel workflow serve. Un token di deploy in grado di amministrare l'intero account cloud non supera questo test.
Mercoledì: script injection
I valori di contesto non attendibili, come il titolo di una pull request, diventano un problema quando vengono inseriti direttamente in uno script inline. La guida di GitHub indica due modi per evitarlo.
L'approccio raccomandato è usare un'action che riceva il valore di contesto come argomento, perché in questo modo il valore non viene mai usato per generare uno script di shell. Se deve mantenere uno script inline, imposti l'espressione come variabile d'ambiente intermedia:
- name: Check PR title
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
if [[ "$TITLE" =~ ^octocat ]]; then exit 0; else exit 1; fi
Nell'esempio di GitHub, un titolo come a"; ls $GITHUB_WORKSPACE" fa fallire il controllo e il comando non viene eseguito. Il valore è conservato in memoria e usato come variabile, quindi non interagisce con la generazione dello script. GitHub suggerisce inoltre di racchiudere tra doppi apici le variabili di shell per evitare il word splitting, una pratica generale della shell.
Per individuare i casi sospetti, cerchi le espressioni di contesto nei workflow e legga ogni occorrenza nel relativo blocco run::
grep -rn 'github.event' .github/workflows
Giovedì: secret ed environment protetti
Collochi i secret di deployment negli environment e richieda dei reviewer. Un job di workflow non può accedere ai secret di un environment finché un reviewer non lo approva.
GitHub è chiaro sul fatto che la redazione automatica (masking) non è garantita, perché il valore di un secret può essere trasformato in diversi modi. Il masking si basa in larga parte sulla corrispondenza esatta ed è eseguito dal runner, quindi un secret viene oscurato solo se un job lo ha usato e il runner vi ha avuto accesso. Usi questa tabella come checklist di revisione:
| Voce da verificare | Domanda a cui rispondere |
|---|---|
| Secret registrati | Ciascuno è ancora necessario e quando è stato ruotato l'ultima volta? |
| Secret strutturati | Qualche secret è un blob JSON, XML o YAML? Lo si divida in un secret per ogni valore. |
| Valori derivati | Ogni valore codificato in Base64 o URL-encoded, o generato (ad esempio un JWT firmato), è registrato come secret? |
| Altri dati sensibili | I dati che non sono un secret di GitHub sono mascherati con ::add-mask::VALUE? |
| Log di esecuzione | Dopo aver provato input validi e non validi, i secret compaiono nell'output o nei log di errore? |
| Environment | I secret di produzione sono protetti da reviewer obbligatori? |
Gli errori possono finire su STDOUT e STDERR in modi non previsti, quindi dopo i test legga i log a mano. Se un secret non oscurato compare in un log di esecuzione, elimini il log e ruoti il secret. Rimuova i secret non più necessari e ruoti periodicamente gli altri: ciò riduce il tempo in cui un valore compromesso resta valido.
Venerdì: responsabilità su workflow e output
Questa sezione contiene un nostro consiglio. Le fonti citate sopra non lo prescrivono e non è attribuito a GitHub, OWASP o NIST.
Stabilisca chi può modificare i workflow che compilano e rilasciano il suo software, e lo metta per iscritto. Se sulla sua piattaforma usa CODEOWNERS, la directory .github/workflows è un percorso sensato da coprire. Individui poi quale workflow produce gli artefatti di rilascio, dato che sono ciò di cui si fideranno i clienti o i sistemi di produzione.
Sugli artefatti le fonti arrivano solo fino a un certo punto. OWASP elenca la validazione impropria dell'integrità degli artefatti come CICD-SEC-9, e il logging e la visibilità insufficienti come CICD-SEC-10. Il Secure Software Development Framework (SP 800-218) del NIST offre un vocabolario comune che acquirenti e fornitori possono usare per discutere di sviluppo sicuro. Nessuna di queste fonti fornisce una ricetta per la firma degli artefatti, quindi non ne proponiamo una.
Infine, stabilisca chi si accorge delle modifiche a file di workflow, secret o approvazioni degli environment, e in che modo ne viene informato.
Da dove iniziare
- Imposti il permesso predefinito del
GITHUB_TOKENin sola lettura per i contenuti del repository e aggiunga blocchipermissionsai job che richiedono di più. - Elenchi tutte le action in uso, ne legga il codice sorgente e verifichi che non inviino secret a host inattesi né li stampino nei log.
- Cerchi le espressioni di contesto nei blocchi
run:e le sposti in una variabile d'ambiente intermedia, oppure in un'action. - Sposti i secret di produzione in un environment con reviewer obbligatori, divida i secret strutturati in valori singoli, registri i valori derivati e ruoti tutto ciò che è comparso in un log.
- Metta per iscritto chi può modificare i workflow che producono gli artefatti di rilascio e chi viene avvisato quando cambiano.
Se desidera un secondo parere sulle sue pipeline, il nostro lavoro sullo sviluppo software sicuro include la configurazione CI/CD. Un penetration test può mostrare se una pull request è in grado di raggiungere i suoi secret.
