Token, OIDC, pinning e attestation

Un workflow che compila e lancia test può permettersi qualche imprecisione. Nel momento in cui pubblica un pacchetto, apre una pull request o entra in un account cloud, ogni scorciatoia diventa una credenziale da difendere. Questa unità raccoglie le pratiche che l’esame considera non negoziabili quando un workflow tocca sistemi reali.

Il GITHUB_TOKEN e il confronto con un PAT

All’inizio di ogni job GitHub crea automaticamente un GITHUB_TOKEN: un installation access token emesso per quel singolo job e limitato al repository che contiene il workflow. Nasce col job e scade con lui, al massimo sei ore sui runner ospitati da GitHub e ventiquattro su un self-hosted runner. Non va creato, non va ruotato, non va revocato: è la differenza sostanziale rispetto a un personal access token, che appartiene a una persona, dura settimane o mesi, porta con sé l’accesso a tutti i repository di quella persona e sopravvive alla sua uscita dal team.

I permessi si dichiarano con la chiave permissions, a livello di workflow o di singolo job, e il livello job vince su quello workflow. Ogni ambito (contents, pull-requests, issues, packages, id-token, attestations, security-events e altri) accetta read, write o none. La regola da ricordare: se dichiari anche un solo ambito, tutti quelli che non hai nominato diventano none. Esistono le forme abbreviate read-all e write-all, e la mappa vuota che azzera tutto. L’impostazione consigliata parte da sola lettura sui contenuti e alza il permesso job per job.

C’è poi un comportamento che l’esame ama trasformare in scenario: le azioni compiute con il GITHUB_TOKEN in genere non innescano nuove esecuzioni di workflow, per evitare ricorsioni. Se un bot apre una pull request con quel token e la CI non parte, non è un guasto, è il progetto. Le eccezioni sono workflow_dispatch e repository_dispatch. Per far scattare davvero gli altri workflow servono un PAT o un token di GitHub App, con il costo di gestione che comportano.

OIDC al posto delle chiavi statiche

Salvare una chiave AWS o un client secret Azure nei secrets significa custodire per anni una credenziale che vale quanto l’infrastruttura. OpenID Connect elimina il problema: il job chiede al provider OIDC di GitHub un JWT generato al volo, lo scambia con il cloud e riceve una credenziale temporanea. Il permesso necessario è id-token: write; senza quello la richiesta fallisce, ed è la causa più frequente negli scenari d’esame su questo tema.

Il token porta i claim su cui il cloud costruisce la propria trust policy: sub, aud, repository, repository_owner, ref, sha, environment e job_workflow_ref. Il claim sub descrive il workflow chiamante, e qui nasce la confusione tipica. Quando il job gira dentro un reusable workflow, sub continua a raccontare il chiamante, non il workflow riusato: per legare la fiducia proprio al workflow condiviso serve job_workflow_ref, che identifica il workflow effettivamente eseguito. Il supporto ai claim personalizzati non è universale (Google Cloud e HashiCorp Vault lo accettano); dove il provider legge solo i claim standard si personalizza il sub via API perché includa job_workflow_ref.

Pinning, immutabilità e policy sulle action

Scrivere uses: owner/action@v4 è comodo ma mobile: un tag può essere spostato o cancellato, quindi il codice che eseguirai domani non è necessariamente quello che hai letto oggi. Il pinning al commit SHA completo è al momento l’unico modo di usare un’action come rilascio immutabile, perché servirebbe una collisione SHA-1 per cambiarne il contenuto. Si tiene il tag come commento accanto allo SHA, per non perdere leggibilità.

Sul versante di chi pubblica, le immutable releases rendono immodificabile il tag associato a una release di GitHub, mentre i tag Git non legati a una release restano spostabili. Sul versante di chi consuma, l’organizzazione può imporre la regola con Require actions to be pinned to a full-length commit SHA; attenzione però, i reusable workflow possono ancora essere referenziati per tag.

Le policy di organizzazione offrono Allow all, Disable e la variante selettiva, con le opzioni Allow actions created by GitHub e Allow Marketplace actions by verified creators, più una lista di pattern nella forma OWNER/REPOSITORY@TAG-OR-SHA con caratteri jolly. Un chiarimento che vale un punto: il badge Verified creator attesta che GitHub ha verificato l’identità di chi pubblica, non che il codice sia stato revisionato.

Input non fidati e attestation di provenienza

Le espressioni fra doppie graffe vengono valutate e sostituite prima che la shell parta: il valore non arriva come dato, diventa testo dello script. Se quel valore è il titolo di una issue o il nome di un branch, chiunque può iniettare comandi. Da trattare come ostili sono i campi che terminano in body, title, head_ref, label, message, name, ref, email, page_name, default_branch. La mitigazione canonica è passare per una variabile d’ambiente intermedia e citarla nella shell; in alternativa si passa il valore come input a un’action, dove arriva come argomento e non come codice.

permissions:
  contents: read
  id-token: write          # richiesto per ottenere il token OIDC
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@SHA_COMPLETO_40_CARATTERI # v4.2.2
      - name: Controlla il titolo
        env:
          PR_TITLE: ${{ github.event.pull_request.title }}
        run: echo "$PR_TITLE"

Le artifact attestations chiudono il cerchio a valle: l’action di provenienza pubblicata da GitHub firma con Sigstore una dichiarazione che lega il binario al repository, al workflow, al commit e all’evento che lo ha prodotto. Servono id-token: write, attestations: write e contents: read, più packages: write per le immagini container. Da sole valgono SLSA v1.0 Build Level 2; il Build Level 3 richiede un reusable workflow che isoli la build dal workflow chiamante. La verifica avviene con gh attestation verify indicando il repository, e con --predicate-type quando si controlla un SBOM. Ultima precisazione, spesso oggetto di domanda: un’attestation non certifica che l’artefatto sia sicuro, dimostra da dove e come è stato costruito. Il giudizio resta umano.