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
- Variables
- func Cache(home string, f *FetchedPod) (string, error)
- func Fingerprint(pubKeyHex string) (string, error)
- func KnownPublishersPath(home string) string
- func LoadCachedAttestation(home, handle, podName, version string) (*api.PodAttestation, error)
- func LoadManifestParams(home, handle, podName, version string) (feeder.ManifestParams, error)
- func Verify(f *FetchedPod) error
- type FetchedPod
- type FetchedRotation
- type KeyHistoryEntry
- type KnownPublishers
- type Lock
- type PublisherRecord
- type Status
- type TrustMatch
- type TrustStatus
Constants ¶
const (
// KnownPublishersVersion is the on-disk schema tag for v1.
KnownPublishersVersion = "konareef-known-publishers/v1"
)
Variables ¶
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).
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.
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).
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 ¶
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 ¶
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:
- SHA-256(ManifestCanonical) == PodHash — the bytes you received are the bytes that produced the claimed hash.
- The publisher signature is valid over ManifestCanonical under PublisherPubkeyHex — that hash was attested by the publisher who controls PublisherPubkeyHex.
- 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:
- 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.
- Confirms attestation.handle matches the walker's handle argument (defends against cross-publisher attestation reuse).
- Verifies signature_by_old_key under the current pubkey.
- 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 ¶
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.
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 ¶
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.
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 )