Skip to content

Read the release manifest: minisign verify, parse, and cache #395

Description

@onel

Size

M

Area

backend

Depends on

#386

Summary

The control-plane update transaction and its trigger are both built, but nothing picks the target: POST /api/v1/system/update takes two image refs typed by an admin. On an appliance the target comes from a signed release manifest (RELEASE_MANIFEST.md). This slice builds the half that has no network and no timer in it — verify a minisign signature against a list of public keys, parse the manifest, decide whether it applies to this box, and cache the last good one. The hourly poll and the dashboard prompt come after, and both are easier once this part is pure and tested.

Spec / source of truth

docs/specs/RELEASE_MANIFEST.md (whole doc — schema, signing, failure modes); docs/specs/UPDATES.md # 3

Do

New package under internal/hostagent/ (suggest relmanifest), with no HTTP client and no goroutine in it:

  • Verify. minisign signature format (Ed25519), verified against a list of public keys, not one constant — RELEASE_MANIFEST.md # Signing makes key rotation depend on this, and adding the list later is the flag day the design avoids. An empty key list must refuse everything: a box with no baked key ignores every manifest, which is the safe state while no signing key exists.
  • Parse. The v1 schema: manifest_version, channel, brain, ui, minimum_host_agent, released_at, rollback_to. Unknown fields are ignored on purpose (additive evolution does not bump manifest_version).
  • Decide. Given a parsed manifest and this box's state, answer: does it apply (channel match, minimum_host_agent satisfied by the running host-agent), and does it name something other than what is running. minimum_host_agent not satisfied means "honoured as the current release, but no prompt" (# Failure modes) — that is a distinct answer from "does not apply", so do not collapse the two.
  • Cache. Write the last valid manifest plus its signature to /var/lib/malmo/manifest.json, atomically, the way internal/hostagent/controlplane writes its ledger. A box that cannot reach the CDN keeps running on the last known manifest.
  • Table-test the failure modes the spec names: bad signature, signature from a key not in the list, truncated file, wrong manifest_version, wrong channel, minimum_host_agent too high, rollback_to set.

Out of scope, deliberately: the hourly poll, any HTTP, the dashboard prompt, and the three-strikes pin. Also out of scope: turning a version into an image ref — see below, that needs a decision first.

Touch

internal/hostagent/relmanifest/, docs/progress/

Done when

A signed manifest verifies and parses, an unsigned or wrongly-signed one is refused, the decision function answers the three cases the spec names, and the last good manifest survives a restart. No network in the package and no timer.

Two preconditions this slice does not fix (recorded so the next slice does not trip on them)

  1. Nothing can sign a manifest yet. There is no signing key, no releases.malmo.network, and no CDN (RELEASE_MANIFEST.md # Signing defers key custody until there is a release to sign). So this package will be proven against test-generated keys only, and the appliance updater stays inert on a real box until a key is baked in.
  2. The version → image-ref step is undecided. The manifest names versions, not URLs (RELEASE_MANIFEST.md # What the manifest is), the transaction pulls by digest (BUILD.md # 6), and the published images are on ghcr and still privateBUILD.md # 6 records that flipping the two packages to public is a manual, org-admin action that has not happened, so no box can pull them anonymously today. Resolving a version to a digest needs either a registry lookup at update time or digests in the manifest. That is a design call for the maintainer, not something to settle inside this slice.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions