Guides

Guides · Partie 7 sur 8

Ancrer vos versions depuis la CI

Chaque version que vous livrez est un ensemble d’octets : archives tar, wheels, images de conteneur, une plage de commits. Ancrer une preuve d’existence (Proof of Existence, PoE) Label 309 pour ces octets transforme un « croyez-nous sur parole, ce build est bien ce que nous avons publié » en un « voici une transaction Cardano qui en témoigne ». Vous calculez l’empreinte de chaque artefact pour en faire une feuille, vous pliez les feuilles en une seule racine de Merkle, et vous publiez cette unique racine sur la chaîne sous le label de métadonnées 309. Dès lors, quiconque détient la référence de la transaction peut prouver qu’un artefact existait à l’heure de son bloc ou avant, à partir de la seule chaîne publique, sans compte et sans faire confiance à votre pipeline ni à votre fournisseur.

Les octets de l’artefact ne quittent jamais le runner. La CLI calcule les empreintes localement et ne publie que des digests, ce qui rend l’opération sûre pour les dépôts privés et les builds à source fermée : ce qui arrive sur la chaîne est une empreinte de longueur fixe, jamais votre code.

L’outil central : cardanowall attest

Tout sur cette page passe par une seule commande de la CLI cardanowall, indépendante du gateway (crate cardanowall-cli sur crates.io, avec des binaires précompilés dans les versions de label-309-cli). attest est le point d’entrée pensé pour la CI : il calcule l’empreinte de vos entrées, obtient un devis et publie un enregistrement via un gateway, puis attend l’état de cycle de vie que vous demandez.

Pointez-la vers n’importe quel gateway Label 309 avec une URL de base et une clé d’API à portée de publication, puis donnez-lui quelque chose dont calculer l’empreinte :

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 et --api-key se lisent aussi depuis CARDANOWALL_BASE_URL et CARDANOWALL_API_KEY, si bien que les deux disparaissent de la commande en CI, où elles sont définies depuis votre coffre à secrets.

Trois façons de choisir ce que vous ancrez

Définissez exactement une entrée ; le mode en découle.

Fichiers. --paths accepte un chemin littéral ou un motif glob, répétable. Chaque feuille est le SHA-256 des octets d’un fichier. La sélection est dédupliquée et triée octet par octet selon le chemin relatif normalisé, de sorte que le même arbre de travail produit toujours la même racine, quel que soit l’ordre dans lequel le shell développerait un glob. Mettez le glob entre guillemets pour que votre shell ne le développe pas d’abord :

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

Commits. --commits accepte une plage git rev-list ; chaque feuille est le SHA-256 d’un objet commit brut, du plus ancien au plus récent. Cela ancre la provenance de l’historique lui-même. Il faut l’historique git complet sur le runner, car un clone superficiel ne peut pas résoudre la plage :

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

Digests précalculés. --leaf accepte un digest de 64 caractères hexadécimaux que vous avez calculé ailleurs, répétable et conservé dans l’ordre des arguments. Utilisez-le pour ancrer quelque chose que la CLI ne voit jamais comme un fichier, tel que le digest d’une image OCI :

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

Une seule feuille publie un enregistrement à un seul élément ; plusieurs feuilles publient un enregistrement de Merkle dont la racine est ancrée sur la chaîne, la liste des feuilles étant téléversée pour que chaque élément puisse obtenir plus tard un certificat d’inclusion.

Le manifeste et le reçu

En mode fichiers, attest écrit un poe-manifest.json déterministe à côté de sa sortie (renommez-le avec --manifest-out). Le manifeste consigne la correspondance nom-empreinte de chaque fichier ancré, et les mêmes entrées produisent toujours des octets de manifeste identiques. Ajoutez --anchor-manifest pour replier le SHA-256 du manifeste comme feuille finale, de sorte que le lien entre noms de fichiers et empreintes fasse lui-même partie de ce à quoi la racine s’engage.

--receipt-out écrit un reçu JSON versionné portant l’enregistrement, le devis, la transaction et l’instantané de l’attente. Conservez-le comme preuve de votre build : c’est tout ce dont un verify ultérieur a besoin pour retrouver et vérifier l’ancrage. Stockez-le comme artefact de workflow, joignez-le à la version ou committez-le à côté du changelog.

Attente, état en attente et réexécutions

Par défaut, attest attend que la transaction franchisse le seuil de confirmation (--wait confirmed) ; --wait submitted revient dès qu’elle atteint le réseau. L’attente a une échéance (--timeout, 600 secondes par défaut). Si l’échéance expire, les sorties et le reçu sont tout de même écrits et le processus se termine avec le code 3 (en attente) : la publication n’est pas perdue, elle se poursuit sur le gateway, et vous pourrez la revérifier plus tard avec le reçu. Un plafond --max-usd refuse la publication (code de sortie 1, avant tout téléversement) lorsque le devis dépasse votre limite, de sorte qu’un pic de prix ne peut jamais facturer un pipeline par surprise.

Les réexécutions sont sûres par construction. attest n’envoie aucun en-tête d’idempotence par défaut ; à la place, le gateway déduplique les enregistrements identiques octet pour octet, de sorte que réexécuter le même build n’ancre jamais une seconde fois et ne facture jamais deux fois. Ancrez le même dist/ à nouveau et la seconde exécution rejoue le premier enregistrement gratuitement.

GitHub Actions

L’action cardanowall/poe-attest enveloppe la même CLI pour les workflows GitHub. Elle est open source et épinglée côté chaîne d’approvisionnement : elle intègre les digests SHA-256 de la version de la CLI qu’elle exécute et vérifie à la fois l’archive téléchargée et le binaire extrait avant chaque exécution, si bien qu’un asset de version substitué ne peut pas passer.

Enregistrez deux secrets sur le dépôt, GATEWAY_URL (l’URL de base du plan de données de votre gateway, se terminant par /api/v1) et GATEWAY_API_KEY (une clé à portée de publication), puis ancrez vos assets de version à la publication :

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

L’étape écrit le reçu, affiche un récapitulatif avec la transaction et un lien de vérification, et expose des sorties (tx, record-id, verify-url, et d’autres) pour les étapes suivantes.

Signer le flux avec une identité de CI dédiée

Pour rendre les ancrages d’un pipeline attribuables et repérables, signez chaque enregistrement avec une graine d’identité. Le gateway indexe alors l’enregistrement par la clé publique du signataire, de sorte que quiconque peut lister tout l’historique d’un pipeline depuis le flux d’enregistrements du gateway avec ?signer=<public-key>. La signature reste facultative, et les vérificateurs ne l’exigent jamais.

Utilisez une identité dédiée et jetable par pipeline, jamais une graine personnelle. Enregistrez-la comme un secret protégé par environnement pour que seules les exécutions de cet environnement puissent la lire :

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

La graine est masquée dans les journaux et transmise à la CLI uniquement par stdin, jamais sur la ligne de commande, et aucun secret n’atteint jamais le gateway : le calcul de l’empreinte et la signature se font localement, et seuls l’enregistrement et des données publiques sont publiés.

N’ancrez jamais depuis pull_request_target

Ce déclencheur expose les secrets de votre dépôt à du code issu de pull requests forkées. N’ancrez que depuis des événements que vous contrôlez, comme release, push ou workflow_dispatch. Le job minimal ci-dessus n’a besoin que de contents: read ; n’ajoutez contents: write que si vous joignez aussi le reçu à la version.

L’action comporte plus d’entrées que celles montrées ici, dont un certificat d’inclusion par feuille, la jonction d’assets à la version, un plafond de prix et la politique de délai d’attente. Consultez le README de l’action pour l’ensemble complet.

GitLab CI/CD

Sur GitLab, le même wrapper est distribué comme composant CI/CD. Le job s’exécute dans l’image de conteneur de la CLI elle-même, épinglée par version et par digest, donc rien n’est installé à l’exécution — et, comme partout sur cette page, n’importe quel gateway Label 309 convient, hébergé ou auto-hébergé :

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

Ajoutez les secrets comme variables CI/CD dans Settings → CI/CD → Variables, masquées et protégées : CARDANOWALL_API_KEY (la clé à portée de publication) et, seulement si vous signez, CARDANOWALL_SEED — définie sur le projet lui-même, jamais sur un groupe : l’héritage signerait en silence les ancrages de chaque projet enfant avec une seule identité. Par défaut, le job ne s’exécute que dans les pipelines de tags protégés, si bien qu’une référence non protégée ne peut jamais dépenser votre solde ; redéfinissez l’entrée rules pour ancrer sur d’autres événements.

Les résultats reviennent sous forme de rapport dotenv : un job en aval qui référence le job d’attestation via needs: lit directement la transaction, le lien de vérification et le reste des dix-huit variables POE_* :

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

Le composant comporte plus d’entrées que celles montrées ici, dont les certificats d’inclusion, un plafond de prix, les tags de runner et la politique de délai d’attente. Consultez le README du composant pour l’ensemble complet.

Autres systèmes de CI

La même CLI s’exécute partout. Utilisez l’image de conteneur ghcr.io/cardanowall/label-309-cli (dont l’entrypoint est cardanowall) ou un binaire précompilé depuis la page des versions.

N’importe quel autre runner, avec le binaire dans le 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.

Ce qu’il vous faut d’un gateway

Publier place une transaction sur Cardano, et cela coûte des frais, si bien qu’attest a besoin d’un gateway par lequel soumettre. N’importe quel gateway Label 309 convient : un opérateur hébergé, ou votre propre gateway auto-hébergé (le label-309-gateway open source, un binaire Rust plus Postgres). Depuis la CI, il ne vous en faut que deux choses : une URL de base du plan de données, et une clé d’API à portée de publication (poe:create) adossée à un solde prépayé.

Le gateway détient le portefeuille Cardano approvisionné et paie les frais depuis son propre modèle de solde. Votre CI ne détient aucune clé de portefeuille ni aucun fonds sur la chaîne. Le pire qu’une clé d’API divulguée puisse faire, c’est dépenser le solde prépayé de ce compte en ancrages supplémentaires ; elle ne peut ni déplacer des fonds, ni lire votre contenu, ni signer en votre nom. Renouvelez-la ou révoquez-la à tout moment.

Vérifier l’ancrage

Cet ancrage n’a de valeur que parce que quiconque peut le vérifier sans vous. Munie de la référence de transaction issue du reçu, la vérification s’exécute de façon autonome contre la chaîne publique et un explorateur de votre choix, sans compte et sans gateway :

cardanowall verify <tx-hash>

Elle résout la transaction, valide structurellement l’enregistrement, vérifie une éventuelle signature, confirme que l’enregistrement est établi, et renvoie un verdict comme code de sortie, si bien qu’elle s’insère dans une vérification en aval aussi proprement qu’attest s’insère du côté publication. Pour confirmer un artefact face à son ancrage, calculez l’empreinte du fichier et comparez, ou pour un enregistrement de Merkle, construisez un certificat d’inclusion qui rattache un artefact à la racine publiée. Le modèle complet du vérificateur figure dans Vérification.

La preuve survit au pipeline

Un ancrage Label 309, ce sont de simples métadonnées sous le label 309, pas un reçu de fournisseur. Longtemps après que le runner a disparu, que le registre a été renouvelé et que le système de CI n’est plus qu’un souvenir, la transaction témoigne toujours que vos artefacts existaient à l’heure de leur bloc. Quiconque peut la vérifier depuis la chaîne publique, sans compte et sans faire confiance à celui qui l’a publiée.