Skip to content

feat(catalog): publish a signed catalog for srelens hosts (srelens/srelens#559) - #3

Merged
deveshk0 merged 1 commit into
mainfrom
feat/signed-catalog
Sep 27, 2026
Merged

deveshk0 merged 1 commit into
mainfrom
feat/signed-catalog

Conversation

@deveshk0

Copy link
Copy Markdown
Contributor

Publishes catalog.signed.json, the catalog srelens hosts read from srelens/srelens#559 on (srelens/srelens#745). It is step 2 of that PR's rollout: the key ceremony is done, and the root it produced is pinned in srelens/srelens@43eaace5.

What changes

  • .github/workflows/sign-catalog.yml signs catalog.json into catalog.signed.json with the CATALOG_SIGNING_PRIVATE_KEY secret (already set), then commits it to main. It runs:

    • when catalog.json, publishers/**, trust/**, the scripts or the workflow change on main;
    • every Monday;
    • on demand.

    Each signed catalog gets the signing time (Unix seconds) as its version and expires 30 days later. The key goes into a umask 077 file under $RUNNER_TEMP, which is deleted when the step exits. Runs are serialised, so two runs can't publish out of order.

  • scripts/verify-catalog.mjs checks the result the way a host would, and the workflow publishes nothing it refuses:

    • trust/root.json signs itself at its root threshold;
    • the catalog and every delegation are signed by that root's catalog role, at threshold;
    • the document is a schema 2 catalog, not expired, and at a version higher than the published one;
    • its entries are exactly catalog.json's.
  • trust/root.json and scripts/trust.mjs are byte-identical copies of crates/registry/src/extensions/trust/root.json and scripts/extensions/trust.mjs in srelens/srelens.

  • publishers/srelens.json delegates org.srelens to the srelens release key. It is the same document as the one pinned in crates/registry/src/extensions/trust/publishers.json.

  • README: a "Signed catalog" section.

catalog.json and entries/ are unchanged. Hosts released before #559 go on reading them.

Verification

  • A full dry run of the workflow's steps with throwaway keys. These cases were accepted:
    • a first run with no previous catalog;
    • a normal run;
    • a newer run checked against the previous one.
  • These cases were refused, each with a clear message:
    • a rolled-back version;
    • the same version again;
    • the wrong catalog key;
    • an expired catalog;
    • entries changed after signing;
    • a delegation signed by another key.
  • A catalog produced by the dry run was fed to the srelens host's catalog code (SharedCatalog under TrustRoot::from_signed_documents with that root and delegation), and the host accepted it.
  • python3 -m unittest discover -s tests and python3 scripts/catalog.py --check pass.

After merge

The push run publishes catalog.signed.json to main. The job asks for contents: write, because the repository's default token is read-only. main has no branch protection, so the bot's push goes through. Once the file is live, the "Extension catalog / live catalog and signatures" check on srelens/srelens#745 should pass on a re-run.

Follow-ups

  • Before any key-named release is listed: catalog.json has to be frozen and the signed catalog given its own source. Hosts before #559 can read only bare 64-byte signatures. Every release listed today uses that form, so signing catalog.json as-is is safe for now. The Flux and Argo CD workflows switch to key-named signatures only after the host release.
  • Docs link: the README links to docs/extensions/trust.md on srelens/srelens dev, which exists once #745 merges.

Part of srelens/srelens#559.

…elens#559)

srelens hosts from srelens/srelens#559 on no longer read catalog.json. They
read catalog.signed.json: a DSSE envelope over the same entries, signed with
the catalog key their pinned root names, carrying the publisher delegations,
a version they refuse to see go backwards, and an expiry after which they
stop offering installs. Nothing published it yet, so those hosts would keep
no catalog at all.

A new workflow signs catalog.json into catalog.signed.json with the
CATALOG_SIGNING_PRIVATE_KEY secret whenever the catalog, a delegation, the
root or the scripts change on main, and every Monday, using the signing time
as the version and a 30-day expiry. Before committing, verify-catalog.mjs
checks the result as a host would: the copied root signs itself at its
threshold, the catalog and every delegation are signed by its catalog role,
the version is higher than the published one, the expiry is ahead, and the
entries are exactly catalog.json's. A catalog it refuses is not published.

trust/root.json and scripts/trust.mjs are copies of the pinned root and the
signing script in srelens/srelens. publishers/srelens.json delegates
org.srelens to the srelens release key. catalog.json and its entries are
unchanged, for hosts released before #559.
@deveshk0
deveshk0 merged commit 9c2e4c4 into main Sep 27, 2026
1 check passed
@deveshk0
deveshk0 deleted the feat/signed-catalog branch September 27, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant