slice 10B: operator-signed destructive completion (offline key + signing CLI) (v0.16.0)

A destructive op runs ONLY on a pinned-key-verified, nonce-fresh, in-window,
host-bound, durable-id-bound operator signature. New cmd/felhom-opsign signs
canonical OpBlobs offline via ssh-keygen -Y sign (hardware-ready); the signing
key is never in the hub or agent. New internal/signedjobs runner verifies each
queued blob through the gate and only on all-pass runs the WipeExecutor, which
re-resolves the DURABLE device id + re-inspects (8C) before mkfs — closing the
8C data-bearing-wipe pending_signature gap. New storage durable-device
resolution; authz.CanonicalBlob promoted to production. Real-crypto tests assert
valid executes and forged/replay/expired/retarget/non-pinned are rejected
(executor never called).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 20:14:16 +02:00
parent 8ecf8929fb
commit 588fed2aa9
19 changed files with 1523 additions and 68 deletions
+6 -1
View File
@@ -84,7 +84,10 @@ type Options struct {
// cpu-temp) + per-storage capacity, host-wide and token-authed (one-customer-per-host). When
// nil the endpoint reports "not configured" (host still reports/reconciles).
HostMetrics HostMetricsProvider
Logger *slog.Logger
// HostID is this agent's host id — surfaced in a data-bearing-format pending-op so the operator
// signs an op bound to THIS host (slice 10B anti-retarget). Optional (only used for the hint).
HostID string
Logger *slog.Logger
}
// defaultBackupCadence is the fallback /backup/due window when none is configured.
@@ -129,6 +132,7 @@ type Server struct {
guestList GuestLister // slice 8C (optional)
hostMetrics HostMetricsProvider // slice 9 (optional)
hostID string // slice 10B: for the data-bearing-format pending-op hint
jobsMu sync.Mutex
jobs map[int]*backupJob // per-guest backup job state (slice 8B)
@@ -166,6 +170,7 @@ func NewServer(o Options) (*Server, error) {
diskGate: o.DiskGate,
guestList: o.Guests2,
hostMetrics: o.HostMetrics,
hostID: o.HostID,
jobs: map[int]*backupJob{},
}, nil
}