ProdgatorDocs
Provenance

Attestations

Evidence Prodgator collects, signed attestations on Enterprise, and how to verify them offline.

Pas encore traduite. Cette page est affichée en anglais. Lire l’original en anglais.

Evidence Prodgator collects

On Business and Enterprise, Provenance joins recorded checks, reviews, reports and deployments into a change timeline. Retained events are checked against a stored hash chain, with integrity and retention status shown on the detail page.

Run reports can supply test results, coverage, scans, software bills of materials and build provenance. These reports are input evidence. Uploading a build provenance document does not make it a Prodgator-signed attestation.

Build provenance reports

Build provenance reports accept a Sigstore bundle, DSSE envelope, in-toto statement or a summary from another producer. The report client extracts only the SLSA source and subject fields. A passing report needs SHA-256 subjects and a source commit and repository that match the authenticated run. Missing or different source identities fail. See Build provenance reports.

Prodgator verifies the Sigstore signature of a provenance bundle sent with the report: the signature, the certificate chain, the signing time, the GitHub Actions issuer, and the repository and commit the certificate names. The result shows on the report and in policy results, with the reason when a signature does not verify. See Signature checks. A verified signature does not make a fork or other untrusted run trusted.

Signed attestations

On Enterprise, Prodgator signs three kinds of in-toto statements. Each one comes as a DSSE envelope signed with an ECDSA P-256 key held in AWS KMS. The private key never leaves KMS.

StatementRequestPredicate typeSubject
Change record, the landed commit
Promotion path, the deployed commit and its tree when known
Verification summary, the deployed commit

You request statements with an organization API key that has the scope. Create the key in Organization > API keys (see API keys) and send it as , as described in the public REST API. The statement routes need the Enterprise plan.

Each request returns . is the SHA-256 of the decoded payload. The organization always comes from the API key, and a change or deployment of another organization answers 404.

curl -s -H "Authorization: Bearer $PRODGATOR_API_KEY" "https://api.prodgator.io/v1/provenance/changes/$CHANGE_ID/statement" > change-statement.json

curl -s -H "Authorization: Bearer $PRODGATOR_API_KEY" "https://api.prodgator.io/v1/provenance/deployments/$DEPLOYMENT_ID/lineage/statement" > lineage-statement.json

curl -s -H "Authorization: Bearer $PRODGATOR_API_KEY" "https://api.prodgator.io/v1/provenance/deployments/$DEPLOYMENT_ID/verification" > verification-statement.json

Below, stands for whichever of these files you are checking.

Errors use the v1 error envelope. The cause of a refusal is in . A missing key answers 401, an invalid key or a key without the scope 403, and a plan without signed attestations 402 .

Every statement is bound to your organization: the change record and promotion path predicates carry , every predicate carries the provider's , and the verification summary's is . Check these, the subject name and the subject's , so a statement signed for another organization, repository or commit is not accepted in place of yours.

Change record. The predicate holds the change's facts (verdict, bypasses, landing, first protected deployment), every retained event as stored so the hash chain can be checked again, the event count, the chain head, the sequence number the retained events start from, the promotion path when it is ready, and links to the run reports that supplied build provenance. Events carry their source keys () and dedupe keys () because the chain hash covers them; only your organization's API keys with the scope can request the statement, and only on the Enterprise plan.

Only events whose actor is a provider account (a login on GitHub, GitLab, Bitbucket or Azure DevOps) or Prodgator itself keep their actor, facts, source and dedupe key. Every other event is redacted: an acknowledgement, a promotion path override, a review given in Prodgator, a deployment event that lists who approved or rejected it in Prodgator, and any event whose actor is not one its type allows. A redacted event keeps only , , , , , and . Its hash cannot be recomputed without the removed fields, so a verifier skips that step for it; the next event's and the signed still pin its hash.

Prodgator refuses to sign a record whose stored chain does not verify (, reason ), a change that has not landed (, reason ), a landed commit that is not a full commit id (, reason ), and a record whose events have all expired under retention (, reason ).

Promotion path. The hops from the release back to where the content was first changed, the environments it passed, evidence counts, the declared promotion path trace, the stored promotion path , and any override of a promotion path rule (path, time and rules; not who, why or the override's source id). A deployment whose promotion path is still being computed answers with reason , and one whose commit is not a full commit id answers with reason .

Verification summary. A SLSA verification summary for a deployment to a protected environment. lists the change record and promotion path statements it was built from, each as an identifier (a URL) and the SHA-256 of that statement. The URLs only name the input statements; there is nothing to fetch from them. Request the statements from the routes above and compare their . is one descriptor for the policies page () whose lists each release policy the deployment's latest evaluation applied as , with a SHA-256 over that list as its digest.

The result is only when all of these hold:

  • The deployment succeeded, and neither its required reviewers nor Prodgator's rule rejected it or is still waiting.
  • Its latest release policy evaluation approved it, or no release policy evaluated it.
  • The deployment's change is verified, is not an expected bypass and records no bypass.
  • The deployment was released without any override: release policy override, release past approval, or promotion path override.
  • Prodgator checked every open change the deployed commit may contain, for that exact commit. Until that check has finished, the summary is : right after a deployment is reported, when the provider could not answer, when the repository has more than 50 open changes, or when the environment was not protected when the deployment ran. A later check of that deployment, such as its next event, replaces the result.
  • The promotion path is ready, complete, not cut short, and the content of every hop is known.

Anything else is , including a change that has not landed or a promotion path that is not ready yet (the missing statement is then left out of ). is the deployment time. The route answers with reason outside a protected environment and with reason without a change record. It answers and signs nothing for reason (the change record does not verify), (the deployed or landed commit is not a full commit id), (the deployment has not succeeded) and (every event of the change record expired).

Prodgator signs a statement the first time it is requested and keeps the envelope for 366 days, so later requests return the same signature while the record is unchanged. A record that changes (a new event, a recomputed promotion path) gives a new statement with a new digest, signed on its next request. Before serving a kept envelope, Prodgator checks its signature against the listed keys and signs again if it does not verify. The first export of each statement is written to the audit log (Organization > Audit log) as Signed Attestation Exported, once its envelope is stored, and names the API key that requested it. If signing or storage is unavailable, the request fails with and nothing unsigned is returned. A statement larger than 4 MB answers with reason .

A signed envelope cannot be revoked and carries no expiry. A statement signed today stays a valid signature even after the record changes, for example when a bypass is found later or the promotion path is recomputed. Request the verification summary close to when you use it, and check , rather than reusing an old download.

Verifying a signed attestation

Verification needs only the envelope and the public keys. No call to Prodgator is needed after you download them.

  1. Download the public keys with (). A key with the scope can read them on any plan that includes API access. The response is , and each key is . After a key rotation the previous key stays listed, current key first.
  2. Find the key whose matches the envelope's .
  3. Decode from base64 and build the DSSE pre-authentication encoding: , lengths in bytes.
  4. Verify (base64, DER-encoded ECDSA) over that encoding with the key, using SHA-256.
  5. Check the statement is yours and about the commit you mean: the subject name, the subject's (the full commit id you are about to deploy), and (change record, promotion path) or the ending in (verification summary). A verification summary's subject name is the branch, so the name alone matches every summary of that branch: an old summary would pass for a newer commit unless you check the digest. The same holds for a promotion path, whose subject name is the environment: check its too.
  6. For a change record, check the event chain: each event's is the SHA-256 of the canonical JSON (keys sorted, no whitespace) of the event without , and its is the previous event's . Skip the hash recomputation for a redacted event, but still check its links. The first event's is the change id when is 1. The last event's must equal and its must equal . When is greater than 1, earlier events expired under retention and the chain is checked from there.
  7. For a verification summary, read : a valid signature on a summary is a valid record of a failed verification.

A minimal check of steps 2 to 4 with Node.js:

import { createPublicKey, verify } from 'node:crypto';
import { readFileSync } from 'node:fs';

const { envelope } = JSON.parse(readFileSync('statement.json', 'utf8')).data;
const { keys } = JSON.parse(readFileSync('keys.json', 'utf8')).data;

const payload = Buffer.from(envelope.payload, 'base64');
const type = Buffer.from(envelope.payloadType, 'utf8');
const pae = Buffer.concat([
  Buffer.from(`DSSEv1 ${type.length} `), type,
  Buffer.from(` ${payload.length} `), payload,
]);
const ok = envelope.signatures.some(({ keyid, sig }) => {
  const key = keys.find((k) => k.keyid === keyid && k.alg === 'ecdsa-p256-sha256');
  return key && verify('sha256', pae, { key: createPublicKey(key.pem), dsaEncoding: 'der' }, Buffer.from(sig, 'base64'));
});
console.log(ok ? 'Signature valid' : 'Signature not valid');

A valid signature proves Prodgator signed this statement. It does not prove more than the statement says: read and the change verdict, not just the signature.

Prodgator's verify script () runs all of these checks offline: . It takes the response body saved from the route (with its wrapper) or a bare envelope, and the keys response, and checks when present. , and fail when the statement is bound to another organization, names another subject or is about another commit (a full 40 or 64 character commit id, compared with the subject's ). Pass for a promotion path as well: its subject name is the environment, so the name alone matches every promotion path to that environment. A verification summary needs , and one that is not exits with code 1, unless you pass . Exit codes: 0 valid, 1 a check failed, 2 wrong arguments.

Sur cette page