Guide

Guide · Parte 7 di 8

Ancorare le release dalla CI

Ogni release che distribuisci è un insieme di byte: tarball, wheel, immagini di container, un intervallo di commit. Ancorare una Proof of Existence (prova di esistenza, PoE) Label 309 per quei byte trasforma «fidati della nostra parola: la build è ciò che abbiamo pubblicato» in «ecco una transazione Cardano che lo attesta». Calcoli l’hash di ogni artefatto per ottenere una foglia, pieghi le foglie in un’unica radice di Merkle e pubblichi quell’unica radice sulla catena sotto l’etichetta di metadati 309. Da quel momento chiunque abbia il riferimento della transazione può dimostrare che un artefatto esisteva all’orario del suo blocco o prima, dalla sola catena pubblica, senza account e senza fidarsi della tua pipeline o del tuo fornitore.

I byte dell’artefatto non lasciano mai il runner. La CLI calcola gli hash localmente e pubblica solo i digest, quindi l’operazione è sicura per repository privati e build a codice chiuso: ciò che finisce sulla catena è un hash di lunghezza fissa, mai il tuo codice.

Lo strumento centrale: cardanowall attest

Tutto in questa pagina passa da un unico comando della CLI cardanowall, indipendente dal gateway (crate cardanowall-cli su crates.io, con binari precompilati nelle release di label-309-cli). attest è il punto di ingresso pensato per la CI: calcola l’hash dei tuoi input, richiede un preventivo e pubblica un record attraverso un gateway, e attende lo stato del ciclo di vita che indichi.

Puntalo a un qualsiasi gateway Label 309 con un URL di base e una chiave API con ambito di pubblicazione, poi dagli qualcosa di cui calcolare l’hash:

export CARDANOWALL_API_KEY="…"   # a publish-scoped key from your gateway

cardanowall attest \
  --paths 'dist/*' \
  --base-url https://your-gateway.example/api/v1 \
  --wait confirmed \
  --receipt-out poe-receipt.json

--base-url e --api-key si leggono anche da CARDANOWALL_BASE_URL e CARDANOWALL_API_KEY, quindi entrambi spariscono dal comando in CI, dove vengono impostati dal tuo archivio dei segreti.

Tre modi per scegliere cosa ancorare

Imposta esattamente un input; la modalità ne consegue.

File. --paths accetta un percorso letterale o un pattern glob, ripetibile. Ogni foglia è lo SHA-256 dei byte di un file. La selezione viene deduplicata e ordinata byte per byte per percorso relativo normalizzato, così lo stesso albero di lavoro produce sempre la stessa radice, indipendentemente dall’ordine in cui la shell espanderebbe un glob. Metti il glob tra apici affinché la tua shell non lo espanda prima:

cardanowall attest --paths 'dist/**/*.tar.gz' --paths 'dist/**/*.whl'

Commit. --commits accetta un intervallo git rev-list; ogni foglia è lo SHA-256 di un oggetto commit grezzo, dal più vecchio al più recente. Questo ancora la provenienza della storia stessa. Richiede la storia git completa sul runner, poiché un clone superficiale non può risolvere l’intervallo:

cardanowall attest --commits v1.0.0..v1.1.0

Digest precalcolati. --leaf accetta un digest di 64 cifre esadecimali che hai calcolato altrove, ripetibile e mantenuto nell’ordine degli argomenti. Usalo per ancorare qualcosa che la CLI non vede mai come file, come il digest di un’immagine OCI:

cardanowall attest --leaf 9f86d0818840…0a08   # a 64-hex digest, e.g. an image digest

Una singola foglia pubblica un record a un solo elemento; più foglie pubblicano un record di Merkle la cui radice è ancorata sulla catena, con l’elenco delle foglie caricato affinché ogni elemento possa in seguito ottenere un certificato di inclusione.

Il manifest e la ricevuta

In modalità file, attest scrive un poe-manifest.json deterministico accanto al proprio output (rinominalo con --manifest-out). Il manifest registra la corrispondenza nome-hash di ogni file ancorato, e gli stessi input producono sempre byte di manifest identici. Aggiungi --anchor-manifest per ripiegare lo SHA-256 del manifest come foglia finale, così che il legame tra nomi di file e hash faccia a sua volta parte di ciò a cui la radice si impegna.

--receipt-out scrive una ricevuta JSON versionata che riporta il record, il preventivo, la transazione e l’istantanea dell’attesa. Conservala come prova della tua build: è tutto ciò di cui un verify successivo ha bisogno per trovare e controllare l’ancoraggio. Salvala come artefatto del workflow, allegala alla release oppure inseriscila in un commit accanto al changelog.

Attesa, stato in sospeso e riesecuzioni

Per impostazione predefinita attest attende che la transazione superi la soglia di conferma (--wait confirmed); --wait submitted ritorna non appena raggiunge la rete. L’attesa ha una scadenza (--timeout, 600 secondi per impostazione predefinita). Se la scadenza passa, gli output e la ricevuta vengono comunque scritti e il processo termina con 3 (in sospeso): la pubblicazione non va persa, prosegue sul gateway, e potrai ricontrollarla più tardi con la ricevuta. Un tetto --max-usd rifiuta la pubblicazione (codice di uscita 1, prima di qualsiasi caricamento) quando il preventivo supera il tuo limite, così un picco di prezzo non può mai addebitare a sorpresa una pipeline.

Le riesecuzioni sono sicure per costruzione. attest non invia alcun header di idempotenza per impostazione predefinita; il gateway deduplica invece i record identici byte per byte, così rieseguire la stessa build non ancora mai una seconda volta e non addebita mai due volte. Ancora di nuovo lo stesso dist/ e la seconda esecuzione riproduce il primo record senza costi.

GitHub Actions

L’azione cardanowall/poe-attest avvolge la stessa CLI per i workflow di GitHub. È open source e vincolata sulla catena di fornitura: incorpora i digest SHA-256 della release della CLI che esegue e verifica sia l’archivio scaricato sia il binario estratto prima di ogni esecuzione, così un asset di release sostituito non può passare.

Salva due segreti nel repository, GATEWAY_URL (l’URL di base del piano dati del tuo gateway, che termina con /api/v1) e GATEWAY_API_KEY (una chiave con ambito di pubblicazione), poi ancora i tuoi asset di release alla pubblicazione:

name: anchor-release
on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  attest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: cardanowall/poe-attest@v1
        with:
          gateway-url: ${{ secrets.GATEWAY_URL }}
          api-key: ${{ secrets.GATEWAY_API_KEY }}
          paths: |
            dist/**/*.tar.gz
            dist/**/*.whl

Il passo scrive la ricevuta, stampa un riepilogo con la transazione e un link di verifica, ed espone output (tx, record-id, verify-url e altri) per i passi successivi.

Firmare il flusso con un’identità di CI dedicata

Per rendere gli ancoraggi di una pipeline attribuibili e reperibili, firma ogni record con un seed di identità. Il gateway indicizza allora il record per la chiave pubblica del firmatario, così chiunque può elencare l’intera storia di una pipeline dal feed dei record del gateway con ?signer=<public-key>. La firma resta facoltativa, e i verificatori non la richiedono mai.

Usa un’identità dedicata e usa-e-getta per ogni pipeline, mai un seed personale. Salvala come segreto protetto dall’ambiente, così che solo le esecuzioni di quell’ambiente possano leggerla:

jobs:
  attest:
    runs-on: ubuntu-latest
    environment: release-signing
    steps:
      - uses: actions/checkout@v7
      - uses: cardanowall/poe-attest@v1
        with:
          gateway-url: ${{ secrets.GATEWAY_URL }}
          api-key: ${{ secrets.GATEWAY_API_KEY }}
          seed: ${{ secrets.CI_SIGNING_SEED }}
          paths: dist/**/*.tar.gz

Il seed viene mascherato nei log e passato alla CLI solo tramite stdin, mai sulla riga di comando, e nessun segreto raggiunge mai il gateway: il calcolo dell’hash e la firma avvengono localmente, e vengono pubblicati solo il record e dati pubblici.

Non ancorare mai da pull_request_target

Quel trigger espone i segreti del tuo repository a codice proveniente da pull request forkate. Ancora solo da eventi che controlli, come release, push o workflow_dispatch. Il job minimo qui sopra necessita solo di contents: read; aggiungi contents: write solo se alleghi anche la ricevuta alla release.

L’azione offre più input di quelli mostrati qui, tra cui un certificato di inclusione per foglia, l’allegato degli asset alla release, un tetto di prezzo e la politica di timeout. Consulta il README dell’azione per l’insieme completo.

GitLab CI/CD

Su GitLab lo stesso wrapper arriva come componente CI/CD. Il job gira dentro l’immagine di container della CLI stessa, bloccata per versione e digest, quindi a runtime non si installa nulla — e, come ovunque in questa pagina, funziona qualsiasi gateway Label 309, gestito da un operatore o self-hosted:

include:
  - component: gitlab.com/cardanowall/poe-attest/attest@1
    inputs:
      gateway-url: https://your-gateway.example/api/v1
      paths: |
        dist/**/*.tar.gz
        dist/**/*.whl

Aggiungi i segreti come variabili CI/CD in Settings → CI/CD → Variables, mascherate e protette: CARDANOWALL_API_KEY (la chiave con scope di pubblicazione) e, solo se firmi, CARDANOWALL_SEED — impostala sul progetto stesso, mai su un gruppo: l’ereditarietà firmerebbe in silenzio gli ancoraggi di ogni progetto figlio con una sola identità. Per impostazione predefinita il job gira solo nelle pipeline dei tag protetti, così un ref non protetto non può mai spendere il tuo saldo; sovrascrivi l’input rules per ancorare su altri eventi.

I risultati tornano come report dotenv: un job a valle che referenzia il job di attestazione con needs: legge direttamente la transazione, il link di verifica e le restanti delle diciotto variabili POE_*:

announce:
  needs: [poe-attest]
  script:
    - echo "anchored in $POE_TX"
    - echo "verify at $POE_VERIFY_URL"

Il componente offre più input di quelli mostrati qui, tra cui i certificati di inclusione, un tetto di prezzo, i tag dei runner e la politica di timeout. Consulta il README del componente per l’insieme completo.

Altri sistemi di CI

La stessa CLI gira ovunque. Usa l’immagine di container ghcr.io/cardanowall/label-309-cli (il cui entrypoint è cardanowall) o un binario precompilato dalla pagina delle release.

Qualsiasi altro runner, con il binario nel PATH:

export CARDANOWALL_BASE_URL="https://your-gateway.example/api/v1"
export CARDANOWALL_API_KEY="$YOUR_CI_SECRET"

cardanowall attest \
  --paths 'dist/*' \
  --wait confirmed \
  --receipt-out poe-receipt.json

# exit 0 = reached the wait target; 3 = pending (publish continues on the gateway);
# 1 = refused (for example over --max-usd) or failed.

Cosa ti serve da un gateway

Pubblicare mette una transazione su Cardano, e questo costa una commissione, quindi attest ha bisogno di un gateway attraverso cui inviarla. Va bene qualsiasi gateway Label 309: un operatore ospitato, oppure il tuo gateway self-hosted (il label-309-gateway open source, un binario Rust più Postgres). Dalla CI te ne servono solo due cose: un URL di base del piano dati e una chiave API con ambito di pubblicazione (poe:create), sostenuta da un saldo prepagato.

Il gateway possiede il wallet Cardano finanziato e paga la commissione dal proprio modello di saldo. La tua CI non detiene alcuna chiave del wallet né fondi sulla catena. Il peggio che una chiave API trapelata possa fare è spendere il saldo prepagato di quell’account in altri ancoraggi; non può spostare fondi, leggere i tuoi contenuti o firmare al posto tuo. Ruotala o revocala in qualsiasi momento.

Verificare l’ancoraggio

Quell’ancoraggio vale qualcosa proprio perché chiunque può controllarlo senza di te. Dato il riferimento della transazione preso dalla ricevuta, la verifica gira in modo autonomo contro la catena pubblica e un explorer a tua scelta, senza account e senza gateway:

cardanowall verify <tx-hash>

Risolve la transazione, valida strutturalmente il record, controlla un’eventuale firma, conferma che il record è definitivo e restituisce un verdetto come codice di uscita, così si inserisce in un controllo a valle con la stessa pulizia con cui attest si inserisce sul lato pubblicazione. Per confermare un artefatto rispetto al suo ancoraggio, calcola l’hash del file e confronta, oppure per un record di Merkle costruisci un certificato di inclusione che lega un artefatto alla radice pubblicata. Il modello completo del verificatore è in Verifica.

La prova sopravvive alla pipeline

Un ancoraggio Label 309 è semplice metadato sotto l’etichetta 309, non una ricevuta del fornitore. Molto dopo che il runner è sparito, il registro è stato ruotato e il sistema di CI è solo un ricordo, la transazione attesta ancora che i tuoi artefatti esistevano all’orario del loro blocco. Chiunque può verificarla dalla catena pubblica, senza account e senza fidarsi di chi l’ha pubblicata.