install

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package install: idempotency.go — implements CheckInstalled, the pre-Verify gate that decides whether a fetched pod should be re-installed, treated as already installed (same hash), or refused as a conflict (different hash) against the local cache.

CheckInstalled is read-only against the cache; it never mutates disk. Callers are responsible for acting on the returned Status (typically: print "already installed" + exit 0 on AlreadyInstalled, wrap ErrInstallHashMismatch on Conflict, fall through to Verify + Cache on NotInstalled).

A returned error is reserved for "I could not decide" — e.g. corrupt or partially-written meta.json. Corrupt meta is fail-loud (not silently treated as not-installed) because a partial write from a previous crash is exactly the case StatusConflict exists to prevent silently overwriting.

Package install implements the buyer-side half of the publisher- signing workflow (P0.6 6e): fetch a published pod from reef-core, re-verify that the manifest hashes to the claimed pod_hash and that the publisher signature is valid, and cache the canonical manifest + signature locally so the harness can re-verify at spawn time without a network round-trip.

Three discrete steps, exposed as separate functions so the CLI can surface each as its own confirmation line:

  • Fetch — GET <server>/api/pods/<handle>/<pod>/[latest|<version>]
  • Verify — SHA-256 manifest must equal pod_hash; ECDSA must check out
  • Cache — write to <home>/.konareef/installed/<handle>/<pod>/<version>/

Deliberately *not* in this slice: known_publishers.json TOFU, key rotation, already-installed handling, withdrawn-pod handling, download resumability. They are independent follow-ups (see install-path-design v0); the core verification path lands first.

Package install: lock.go — per-pod advisory locking around the install pipeline.

Concurrent `konareef install <same-handle>/<same-pod>` invocations would otherwise race on the cache directory and on known_publishers.json. AcquireLock serializes them at a per-pod granularity (parallel installs of *different* handles or pods do not block each other).

The lock primitive is platform-specific (see lock_unix.go, lock_windows.go). This file defines the cross-platform surface: AcquireLock returns a *Lock; the caller MUST Release it (typically via defer).

Package install: lock_unix.go — POSIX/BSD flock implementation of the per-pod advisory lock. Kernel releases the lock on FD close, so a kill -9'd install does not strand the next attempt.

manifest_params.go — assemble feeder.ManifestParams from the local install cache (C1 Phase-C, spec §3.2).

`konareef install` writes three files into `<home>/.konareef/installed/<handle>/<pod>/<version>/`:

manifest.canon  — the canonical pod.toml bytes (pre-image of pod_hash)
signature.bin   — the DER-encoded ECDSA signature
meta.json       — pod_hash hex + publisher_pubkey_hex + identifiers

LoadManifestParams reads all three and derives the fields the sidecar's WitnessWriter needs but does NOT get from the reef-core step-disclosure seam: Manifest, Models, Tools, CMax, SigManifest, PkPub. Modeled on LoadCachedAttestation (attestation.go) but returns raw bytes (no wire base64 re-encoding) since these fields feed feeder.WriteWitness, not an HTTP request body.

Index

Constants

View Source
const (
	// KnownPublishersVersion is the on-disk schema tag for v1.
	KnownPublishersVersion = "konareef-known-publishers/v1"
)

Variables

View Source
var ErrContentHashMismatch = errors.New("CONTENT_HASH_MISMATCH: extracted content does not reproduce pod_hash")

ErrContentHashMismatch is returned by Verify when a fetched ContentTarball, extracted and re-run through canon.Canonicalize, does not reproduce PodHash. Like ErrInstallHashMismatch, this is never recoverable at the CLI — the tarball bytes disagree with the signed manifest hash, so nothing is cached. Maps to exit code 5 (local refused).

View Source
var ErrInstallHashMismatch = errors.New("INSTALL_HASH_MISMATCH: version already installed with a different hash")

ErrInstallHashMismatch is the sentinel CLI callers wrap when CheckInstalled returns StatusConflict — that is, when a local meta.json's pod_hash differs from the fetched manifest's pod_hash for the same handle/pod/version. Maps to exit code 5 (local refused); never use --force to bypass.

View Source
var ErrLocalLocked = errors.New("LOCAL_LOCKED: another install of this handle/pod is in progress")

ErrLocalLocked is returned by AcquireLock when another process holds the per-pod advisory lock and the configured timeout elapses. Maps to exit code 5 (local refused).

View Source
var ErrManifestNotFound = errors.New("MANIFEST_NOT_FOUND: no published pod at this URL")

ErrManifestNotFound is returned by Fetch when reef-core responds with HTTP 404 for the requested handle/pod/version. CLI callers MUST use errors.Is(err, ErrManifestNotFound) to detect this case and map it to exit code 4 (upstream missing); the wrapped error retains the requested URL for log triage.

Functions

func Cache

func Cache(home string, f *FetchedPod) (string, error)

Cache writes the verified pod to `<home>/.konareef/installed/<handle>/<podName>/<version>/`, returning the cache-directory path. Three files always land:

  • manifest.canon — the exact canonical bytes (pre-image of pod_hash)
  • signature.bin — the DER-encoded ECDSA signature
  • meta.json — handle, pod name, version, pod_hash hex, publisher pubkey hex (the bits a tool wants without parsing TOML)

When f.ContentTarball is present, a fourth artifact lands: the extracted pod directory under `content/` (Task B3), which `pod run` (Task C2) executes directly rather than re-extracting at spawn time. A legacy fetch with no tarball leaves no `content/` dir at all — manifest-only caching, exactly as before this field existed.

Callers SHOULD only call Cache after Verify returns nil; Cache trusts its input and writes verbatim.

func Fingerprint

func Fingerprint(pubKeyHex string) (string, error)

Fingerprint returns the canonical short identifier for a compressed-secp256k1 pubkey: the first 6 bytes of SHA-256 over the raw key bytes, formatted as 3 groups of 2 bytes joined by `:`. E.g. `02a3:f9b4:c5d6`. Matches the SSH host-key style users already recognise.

func KnownPublishersPath

func KnownPublishersPath(home string) string

KnownPublishersPath returns <home>/.konareef/known_publishers.json.

func LoadCachedAttestation

func LoadCachedAttestation(home, handle, podName, version string) (*api.PodAttestation, error)

LoadCachedAttestation returns the wire-shape attestation for a pod previously cached by `konareef install`. Returns an error if the cache directory is missing or any of the three files ( meta.json, signature.bin) are absent or malformed.

The caller attaches the result to api.SpawnRequest.PodAttestation when spawning the cached pod.

func LoadManifestParams added in v0.1.2

func LoadManifestParams(home, handle, podName, version string) (feeder.ManifestParams, error)

LoadManifestParams returns the feeder.ManifestParams for a pod previously cached by `konareef install`. Returns an error if the cache directory is missing, any of the three files (manifest.canon, signature.bin, meta.json) is absent or malformed, or the manifest has no [model].name.

Manifest is the byte-exact manifest.canon contents — its SHA-256 is the signed h_manifest, so it is never re-serialized. SigManifest is the signature.bin DER padded into the fixed 74-byte PS-1 sig_manifest lane ([derLen][DER][zero pad]) that PS-1 and the verifier expect. PkPub is the 33-byte compressed pubkey, hex-decoded from meta.json. Models is a single-element slice holding [model].name from the manifest. Tools is the declared context.tools source list (empty, non-nil slice when the manifest declares none). CMax is [budget].max_sats.

func Verify

func Verify(f *FetchedPod) error

Verify enforces the cryptographic invariants `konareef install` MUST satisfy before any pod is cached locally:

  1. SHA-256(ManifestCanonical) == PodHash — the bytes you received are the bytes that produced the claimed hash.
  2. The publisher signature is valid over ManifestCanonical under PublisherPubkeyHex — that hash was attested by the publisher who controls PublisherPubkeyHex.
  3. When ContentTarball is present: extracting it and re-running canon.Canonicalize over the result reproduces PodHash too (Task B3) — the actual pod content agrees with what was signed, not just the manifest bytes.

A failure here is **never recoverable** at the CLI — refuse to install and surface the divergence; do not prompt the user past it.

Types

type FetchedPod

type FetchedPod struct {
	Handle             string
	PodName            string
	PodVersion         string
	PodHash            [32]byte
	ManifestCanonical  []byte
	Signature          []byte
	PublisherPubkeyHex string

	// ContentTarball is the gzip tar of the pod directory produced by
	// publish.PackTarball (Task B2), carried through so Verify can
	// reproduce PodHash from the actual content rather than trusting
	// the tarball bytes on a weaker check. nil when the server response
	// omits content_tarball — a legacy (pre-B2) reef-core, or a pod
	// published before content tarballs existed.
	ContentTarball []byte
}

FetchedPod is the parsed wire response from reef-core, decoded into the byte / [32]byte / hex shapes the verifier and the cache consume directly.

func Fetch

func Fetch(serverURL, handle, podName, version string) (*FetchedPod, error)

Fetch GETs `<serverURL>/api/pods/<handle>/<podName>/<version>` and returns the decoded FetchedPod. An empty version resolves to `/latest`, the design's "give me whatever ships today" shortcut.

Any non-2xx status, network failure, JSON parse error, or malformed-byte field is a fatal error. The caller MUST run Verify on the returned FetchedPod before trusting any of its contents — Fetch validates wire-format only, not the cryptographic claims.

type FetchedRotation

type FetchedRotation struct {
	Attestation       identity.RotationAttestation
	SignatureByOldKey []byte
}

FetchedRotation is one chain link as the install client uses it internally — the typed attestation plus the raw signature bytes (base64-decoded from the wire form).

func FetchRotations

func FetchRotations(serverURL, handle string) ([]FetchedRotation, error)

FetchRotations GETs `<serverURL>/api/publishers/<handle>/rotations` and decodes the response into the typed FetchedRotation slice.

An empty rotations array is NOT an error — the publisher may never have rotated. The caller branches on len before invoking WalkRotationChain.

Any non-2xx status, network failure, JSON parse error, or non-base64 signature is a fatal error. The chain walker assumes its inputs are at least well-formed.

func WalkRotationChain

func WalkRotationChain(rotations []FetchedRotation, handle, fromPubkeyHex, toPubkeyHex string) ([]FetchedRotation, error)

WalkRotationChain verifies a chain of rotation hops from fromPubkeyHex to toPubkeyHex for the given handle, returning the hops actually traversed (in order) on success.

At each step the walker:

  1. Finds a hop whose `attestation.old_pubkey_hex` is the current position. If multiple hops have the same old_pubkey (which reef-core's unique_index on (handle, old_pubkey, new_pubkey) does NOT prevent — a key could legitimately have been rotated to two different keys at different times if reef-core overrode the constraint), the first match in the input list wins. The server returns rotations oldest-first.
  2. Confirms attestation.handle matches the walker's handle argument (defends against cross-publisher attestation reuse).
  3. Verifies signature_by_old_key under the current pubkey.
  4. Advances to attestation.new_pubkey_hex.

A maximum-hop guard prevents infinite loops on cyclic input (A→B, B→A, …). The guard fires before pathological CPU use; the resulting error names the cycle as "chain did not reach target".

Errors are descriptive enough for the CLI to surface in the blocking-key-change dialog — they name the offending step.

type KeyHistoryEntry

type KeyHistoryEntry struct {
	PubkeyHex  string     `json:"pubkey_hex"`
	ActiveFrom time.Time  `json:"active_from"`
	ActiveTo   *time.Time `json:"active_to"`
	RotatedTo  *string    `json:"rotated_to"`
}

KeyHistoryEntry is one row in the per-publisher key-rotation log. `active_to == nil` means the key is current; `rotated_to == nil` means we never recorded a successor (e.g., admin recovery wiped it). New entries are appended; existing rows are never mutated.

type KnownPublishers

type KnownPublishers struct {
	Version    string                      `json:"version"`
	Publishers map[string]*PublisherRecord `json:"publishers"`
}

KnownPublishers is the in-memory view of known_publishers.json.

func LoadKnownPublishers

func LoadKnownPublishers(home string) (*KnownPublishers, error)

LoadKnownPublishers reads the known-publishers file. A missing file is NOT an error — it returns a fresh empty struct, so the caller can treat every install as "first contact" until the first Save lands the file on disk.

func (*KnownPublishers) Get

func (k *KnownPublishers) Get(handle string) *PublisherRecord

Get returns the record for handle, or nil if unknown.

func (*KnownPublishers) Match

func (k *KnownPublishers) Match(handle, pubkeyHex string) TrustMatch

Match decides which of the three trust paths an incoming pubkey triggers. The returned TrustMatch.Known is the local record when Status is TrustOK or TrustChange; for TrustNew it is nil.

func (*KnownPublishers) Record

func (k *KnownPublishers) Record(handle, pubkeyHex string, now time.Time)

Record adds or updates the publisher's record.

  • New handle: insert with KeyHistory having one entry.
  • Same pubkey as recorded: idempotent — only LastVerifiedAt refreshes.
  • Different pubkey (rotation / trust override): the previous KeyHistory entry's ActiveTo + RotatedTo are populated, a new entry appended, and the top-level PubkeyHex / Fingerprint / LastVerifiedAt switch to the new key. FirstSeenAt stays at the original value — it tracks the publisher, not the key.

func (*KnownPublishers) Save

func (k *KnownPublishers) Save(home string) error

Save writes the known-publishers file atomically with mode 0644. The parent .konareef directory is created (mode 0700) on first save — identity.json already lives in the same directory, so the permission profile matches.

type Lock

type Lock struct {
	// contains filtered or unexported fields
}

Lock represents a held per-pod advisory lock. Acquired via AcquireLock; released via Release (or via the kernel on FD close, on unix). Release is idempotent and safe on a nil receiver.

func AcquireLock

func AcquireLock(home, handle, podName string, timeout time.Duration) (*Lock, error)

AcquireLock takes an exclusive advisory lock on `<home>/.konareef/installed/<handle>/<podName>/.lock`. If another process holds the lock, AcquireLock polls every 250 ms until the timeout elapses, at which point it returns ErrLocalLocked.

The pod-scoped lock directory (`<handle>/<podName>/`) is created if absent; the version subdirectory is NOT created here (that remains Cache's responsibility).

On windows, this is a no-op + one-time stderr warning; the returned Lock can still be Release'd safely.

func (*Lock) Release

func (l *Lock) Release() error

Release releases the advisory lock and closes the lock file. Safe to call on a nil receiver, and idempotent (a second call is a no-op).

type PublisherRecord

type PublisherRecord struct {
	PubkeyHex      string            `json:"pubkey_hex"`
	Fingerprint    string            `json:"fingerprint"`
	FirstSeenAt    time.Time         `json:"first_seen_at"`
	LastVerifiedAt time.Time         `json:"last_verified_at"`
	KeyHistory     []KeyHistoryEntry `json:"key_history"`
}

PublisherRecord is one publisher's local trust record.

type Status

type Status int

Status is the verdict CheckInstalled returns for a fetched pod against the local cache.

const (
	// StatusNotInstalled — no cache entry for this handle/pod/version.
	// Caller should proceed with Verify + Cache as normal.
	StatusNotInstalled Status = iota
	// StatusAlreadyInstalled — cache entry exists and its pod_hash
	// matches the fetched manifest. Caller should print
	// "already installed" and exit 0; skip Verify + Cache.
	StatusAlreadyInstalled
	// StatusConflict — cache entry exists at this version but its
	// pod_hash differs from the fetched manifest. Caller MUST refuse
	// (do not overwrite) and surface ErrInstallHashMismatch.
	StatusConflict
)

func CheckInstalled

func CheckInstalled(home, handle, podName, version string, fetchedHash [32]byte) (Status, error)

CheckInstalled inspects `<home>/.konareef/installed/<handle>/<podName>/<version>/meta.json` and reports whether a fetched pod with hash `fetchedHash` should be re-installed, treated as already installed, or rejected as a conflict.

Returns:

  • (StatusNotInstalled, nil) when the version directory or meta.json is absent — happy path for first install of this version.
  • (StatusAlreadyInstalled, nil) when the local pod_hash equals fetchedHash — idempotent re-install.
  • (StatusConflict, nil) when the local pod_hash exists and differs from fetchedHash — caller MUST refuse.
  • (StatusNotInstalled, non-nil error) only when meta.json is present but cannot be parsed (corrupt JSON, missing pod_hash field, malformed hex, wrong byte length). Status is always StatusNotInstalled on error paths so callers cannot accidentally act on an indeterminate verdict.

func (Status) String

func (s Status) String() string

String renders a Status for log lines and error messages.

type TrustMatch

type TrustMatch struct {
	Status TrustStatus
	Known  *PublisherRecord // nil iff Status == TrustNew
}

TrustMatch is the structured result of KnownPublishers.Match.

type TrustStatus

type TrustStatus int

TrustStatus is the outcome of matching a fetched pubkey against the local trust record. The three values map 1:1 to the three UX flows in install-path-design v0 §"What the User Sees".

const (
	// TrustNew — no record for this handle. First-contact flow:
	// extra warnings + default-N confirmation prompt.
	TrustNew TrustStatus = iota

	// TrustOK — recorded pubkey matches the fetched one. Repeat-
	// install flow: brief "known publisher" line + default-Y prompt.
	TrustOK

	// TrustChange — recorded pubkey differs from the fetched one.
	// Key-change flow: refuse install with explicit guidance to
	// `konareef pod trust` after out-of-band confirmation.
	TrustChange
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL