feat: keel verify trusts a signature by main or images (decision 0055) #176

Merged
navigator merged 1 commit from feat/images-key-verifiers into main 2026-10-15 15:46:37 -03:00
Owner

Step 4 of handbook decision 0055 (images key). Every verifier must trust the images key before an image artifact is signed with it alone. Until then main and images both sign, in 1 file. This makes keel verify such a verifier. It replaces the "no code change" conclusion of #151.

What changes

  • keel verify checks a signed layer .hash file against /usr/share/keyrings/keel-archive-keyring.gpg (main) and keel-images-keyring.gpg (images), from keel-archive-keyring 0.3.0. A signature by either key is good. The files stay separate. A missing file is skipped.
  • gpgv exits 2 for a file with 2 signatures when one key is in no keyring. signature.verify_by_any reads the status lines one signature at a time: one BADSIG refuses, and one GOODSIG with VALIDSIG by a key that is not revoked or expired is good. The channel pointer path (signature.verify) does not change.
  • A good .hash file gives ok: hash file signed by <fingerprint>, and its digests come from the signed text. A BADSIG is a mismatch. A key in no keyring stays unverified (exit 8, which keel-release step 2 accepts).
  • keel verify --release-manifest FILE verifies FILE against FILE.asc, then the size and sha256 of each listed file that is present. A path that leaves the release directory is refused.
  • --keyring FILE (can repeat) and $KEEL_IMAGE_KEYRINGS replace the 2 defaults.
  • docs/layers.md, README, changelog 0.23.28.

Version

0.23.28, because #175 holds 0.23.27. I merge this after #175, so the changelog gate of #175 does not change. This PR does not publish a release.

Tests

tests/test_layers_rolesig.py, real keys and real gpgv: a dual-signed detached and clear-signed file verifies with each keyring alone; a changed file is BADSIG with either keyring; an unknown or revoked key is refused; a missing keyring is skipped and named; the MANIFEST checks (ok, mismatch, not present, bad path, no .asc); the CLI paths. release_manifest.py, verify.py and the new signature.py code are at 100 percent line coverage.

Step 4 of handbook decision 0055 (images key). Every verifier must trust the images key before an image artifact is signed with it alone. Until then main and images both sign, in 1 file. This makes `keel verify` such a verifier. It replaces the "no code change" conclusion of #151. ## What changes - `keel verify` checks a signed layer `.hash` file against `/usr/share/keyrings/keel-archive-keyring.gpg` (main) and `keel-images-keyring.gpg` (images), from keel-archive-keyring 0.3.0. A signature by either key is good. The files stay separate. A missing file is skipped. - gpgv exits 2 for a file with 2 signatures when one key is in no keyring. `signature.verify_by_any` reads the status lines one signature at a time: one BADSIG refuses, and one GOODSIG with VALIDSIG by a key that is not revoked or expired is good. The channel pointer path (`signature.verify`) does not change. - A good `.hash` file gives `ok: hash file signed by <fingerprint>`, and its digests come from the signed text. A BADSIG is a mismatch. A key in no keyring stays `unverified` (exit 8, which keel-release step 2 accepts). - `keel verify --release-manifest FILE` verifies FILE against FILE.asc, then the size and sha256 of each listed file that is present. A path that leaves the release directory is refused. - `--keyring FILE` (can repeat) and `$KEEL_IMAGE_KEYRINGS` replace the 2 defaults. - `docs/layers.md`, README, changelog 0.23.28. ## Version 0.23.28, because #175 holds 0.23.27. I merge this after #175, so the changelog gate of #175 does not change. This PR does not publish a release. ## Tests `tests/test_layers_rolesig.py`, real keys and real gpgv: a dual-signed detached and clear-signed file verifies with each keyring alone; a changed file is BADSIG with either keyring; an unknown or revoked key is refused; a missing keyring is skipped and named; the MANIFEST checks (ok, mismatch, not present, bad path, no .asc); the CLI paths. release_manifest.py, verify.py and the new signature.py code are at 100 percent line coverage.
feat: keel verify trusts a signature by main or images (decision 0055)
Some checks failed
security / security (pull_request) Successful in 1m28s
tests / etcd (pull_request) Successful in 7m20s
tests / mesh (pull_request) Successful in 11m5s
tests / etcd-auth (pull_request) Successful in 5m15s
tests / vip-upgrade (pull_request) Successful in 7m21s
tests / package (pull_request) Successful in 7s
tests / mariadb-simple (pull_request) Successful in 4m33s
tests / mariadb (pull_request) Successful in 6m4s
tests / vip (pull_request) Failing after 11m47s
tests / postgres (pull_request) Successful in 10m52s
tests / tests (pull_request) Successful in 28m4s
1850960a20
Step 4 of decision 0055. Every verifier must trust the images key before
an image artifact is signed with it alone, and until then main and
images both sign, in 1 file.

- keel verify checks a signed layer .hash file against
  /usr/share/keyrings/keel-archive-keyring.gpg and
  keel-images-keyring.gpg, from keel-archive-keyring 0.3.0. A signature
  by either key is good. A missing file is skipped.
- gpgv exits 2 for a file with 2 signatures when one key is in no
  keyring. signature.verify_by_any reads the status lines one signature
  at a time: one BADSIG refuses, and one GOODSIG with VALIDSIG by a key
  that is not retired is good.
- A good .hash file is "ok: hash file signed by <fingerprint>", and its
  digests come from the signed text. A BADSIG is a mismatch.
- keel verify --release-manifest FILE verifies FILE against FILE.asc,
  then the size and sha256 of each listed file that is present.
- --keyring, which can repeat, and $KEEL_IMAGE_KEYRINGS replace the 2
  default keyrings.

keel pull copies a .hash file into the cache, and keel verify on the
cache checks it. The channel pointer still uses the channel keyring.
navigator force-pushed feat/images-key-verifiers from 1850960a20
Some checks failed
security / security (pull_request) Successful in 1m28s
tests / etcd (pull_request) Successful in 7m20s
tests / mesh (pull_request) Successful in 11m5s
tests / etcd-auth (pull_request) Successful in 5m15s
tests / vip-upgrade (pull_request) Successful in 7m21s
tests / package (pull_request) Successful in 7s
tests / mariadb-simple (pull_request) Successful in 4m33s
tests / mariadb (pull_request) Successful in 6m4s
tests / vip (pull_request) Failing after 11m47s
tests / postgres (pull_request) Successful in 10m52s
tests / tests (pull_request) Successful in 28m4s
to 48212cb6e8
Some checks failed
security / security (pull_request) Successful in 1m32s
tests / etcd (pull_request) Successful in 6m56s
tests / vip (pull_request) Successful in 7m2s
tests / mesh (pull_request) Successful in 11m4s
tests / vip-upgrade (pull_request) Failing after 7m47s
tests / etcd-auth (pull_request) Successful in 5m24s
tests / package (pull_request) Successful in 8s
tests / mariadb (pull_request) Successful in 5m47s
tests / mariadb-simple (pull_request) Successful in 4m44s
tests / tests (pull_request) Has been cancelled
tests / postgres (pull_request) Has been cancelled
2026-10-15 14:40:19 -03:00
Compare
navigator force-pushed feat/images-key-verifiers from 48212cb6e8
Some checks failed
security / security (pull_request) Successful in 1m32s
tests / etcd (pull_request) Successful in 6m56s
tests / vip (pull_request) Successful in 7m2s
tests / mesh (pull_request) Successful in 11m4s
tests / vip-upgrade (pull_request) Failing after 7m47s
tests / etcd-auth (pull_request) Successful in 5m24s
tests / package (pull_request) Successful in 8s
tests / mariadb (pull_request) Successful in 5m47s
tests / mariadb-simple (pull_request) Successful in 4m44s
tests / tests (pull_request) Has been cancelled
tests / postgres (pull_request) Has been cancelled
to efead4e607
All checks were successful
security / security (pull_request) Successful in 1m57s
tests / etcd (pull_request) Successful in 7m26s
tests / vip (pull_request) Successful in 7m13s
tests / mesh (pull_request) Successful in 10m58s
tests / etcd-auth (pull_request) Successful in 5m11s
tests / vip-upgrade (pull_request) Successful in 7m53s
tests / package (pull_request) Successful in 7s
tests / mariadb (pull_request) Successful in 5m49s
tests / mariadb-simple (pull_request) Successful in 4m41s
tests / postgres (pull_request) Successful in 10m29s
tests / tests (pull_request) Successful in 28m25s
2026-10-15 15:05:08 -03:00
Compare
Sign in to join this conversation.
No description provided.