feat(trust): verify release-root signature on the OTA manifest
check_for_updates now fetches the manifest as raw JSON and runs trust::verify_detached before parsing: a tampered or wrong-signer signature rejects the mirror outright, and unsigned manifests are offered for MANUAL apply only — the 3 AM auto-apply scheduler refuses them, closing the unattended remote-root hole (§A of the 1.8.0 hardening plan). UpdateState gains manifest_signed so the UI can surface authenticity. Publisher side: create-release.sh signs the manifest during the release (ceremony, mnemonic via TTY/env only), publish-release-assets hard-refuses to ship an unsigned manifest (grep + new 'ceremony verify' cryptographic gate), and scripts/sign-manifest.sh covers re-signing outside a release run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1977bdefb5
commit
51647b21cd
@@ -48,12 +48,16 @@ arbitrary app catalog to the entire fleet — fully unattended under
|
||||
pinned-anchor binary must actually be built + shipped for enforcement to be live on nodes;
|
||||
(b) flip "accept unsigned" → "reject unsigned" only after the whole fleet is on the pinned
|
||||
binary (`container/app_catalog.rs:397`, the `Unsigned` arm) — see the next item.
|
||||
- [ ] 🔴 **Enforce a signature on the OTA manifest before trusting it.**
|
||||
`update.rs:68` fetches `http://146.59.87.168:3000/.../manifest.json` over cleartext
|
||||
and parses/trusts it with no `trust::verify_detached` call; component sha256/blake3
|
||||
are only checked against that same unauthenticated manifest → remote root RCE.
|
||||
Move to HTTPS + pinned cert, require an Ed25519 release-root signature, and
|
||||
**refuse `auto_apply` until the anchor is pinned.**
|
||||
- [~] 🔴 **Enforce a signature on the OTA manifest before trusting it.** Signature
|
||||
verification LANDED 2026-07-02: `check_for_updates` now fetches raw JSON and runs
|
||||
`trust::verify_detached` — a present-but-invalid/wrong-signer signature hard-rejects
|
||||
the mirror; unsigned manifests are offered for MANUAL apply only (`manifest_signed`
|
||||
surfaced in `UpdateState`) and **auto-apply refuses them**. Publisher side:
|
||||
`create-release.sh` signs the manifest inline (ceremony), `publish-release-assets.sh`
|
||||
hard-refuses to ship unsigned (grep + `ceremony verify` crypto gate), and
|
||||
`scripts/sign-manifest.sh` exists for re-signs. **Still open:** move the mirror
|
||||
to HTTPS + pinned cert (tracked with the next item); flip unsigned-manual-apply →
|
||||
hard-reject once the fleet is on a pinned-anchor binary.
|
||||
- [ ] 🔴 **Implement container image signature verification (cosign).**
|
||||
`container/src/podman_client.rs:255` — `pull_image(.., _signature)` silently discards
|
||||
the signature that the manifest threads all the way down
|
||||
|
||||
Reference in New Issue
Block a user