588fed2aa9
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>
138 lines
5.5 KiB
Go
138 lines
5.5 KiB
Go
package signedjobs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
|
)
|
|
|
|
// fakeWipeOps records Format calls and returns a configurable InspectDevice probe.
|
|
type fakeWipeOps struct {
|
|
probe storage.DeviceProbe
|
|
inspectErr error
|
|
formatCalls []string
|
|
}
|
|
|
|
func (f *fakeWipeOps) InspectDevice(_ context.Context, device string) (storage.DeviceProbe, error) {
|
|
p := f.probe
|
|
p.Device = device
|
|
return p, f.inspectErr
|
|
}
|
|
func (f *fakeWipeOps) Format(_ context.Context, device, _ string) error {
|
|
f.formatCalls = append(f.formatCalls, device)
|
|
return nil
|
|
}
|
|
|
|
// newWipeExec builds a WipeExecutor with injected resolve/derive (so durable resolution is
|
|
// deterministic in a unit test, no real /dev needed).
|
|
func newWipeExec(ops WipeOps, resolve func(string) (string, error), derive func(string) (string, error)) *WipeExecutor {
|
|
w := NewWipeExecutor(ops, quiet())
|
|
w.resolve = resolve
|
|
w.derive = derive
|
|
return w
|
|
}
|
|
|
|
const durable = "byid:wwn-0xtest"
|
|
|
|
func params(t *testing.T, durableID, fstype string) json.RawMessage {
|
|
t.Helper()
|
|
b, _ := json.Marshal(map[string]string{"durable_id": durableID, "fstype": fstype})
|
|
return b
|
|
}
|
|
|
|
// VALID: durable resolves, re-derives to the same id, device is data-bearing → Format runs.
|
|
func TestWipe_ValidExecutes(t *testing.T) {
|
|
ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4"}}
|
|
w := newWipeExec(ops,
|
|
func(id string) (string, error) { return "/dev/sdb", nil },
|
|
func(dev string) (string, error) { return durable, nil })
|
|
if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err != nil {
|
|
t.Fatalf("valid wipe: %v", err)
|
|
}
|
|
if len(ops.formatCalls) != 1 || ops.formatCalls[0] != "/dev/sdb" {
|
|
t.Fatalf("Format calls = %v, want one on /dev/sdb", ops.formatCalls)
|
|
}
|
|
}
|
|
|
|
// PATH-ONLY (no durable_id) → refused, Format NOT called.
|
|
func TestWipe_PathOnlyRefused(t *testing.T) {
|
|
ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}}
|
|
w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil })
|
|
// params carry a raw device path, NOT a durable id.
|
|
p, _ := json.Marshal(map[string]string{"device": "/dev/sdb", "fstype": "ext4"})
|
|
if err := w.Execute(context.Background(), "storage_wipe", p); err == nil {
|
|
t.Fatal("path-only wipe must be refused")
|
|
}
|
|
if len(ops.formatCalls) != 0 {
|
|
t.Errorf("Format was called on a path-only binding: %v", ops.formatCalls)
|
|
}
|
|
}
|
|
|
|
// DURABLE MISMATCH: the resolved device re-derives to a DIFFERENT id → refused (anti-retarget).
|
|
func TestWipe_DurableMismatchRefused(t *testing.T) {
|
|
ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}}
|
|
w := newWipeExec(ops,
|
|
func(string) (string, error) { return "/dev/sdb", nil },
|
|
func(string) (string, error) { return "byid:wwn-0xDIFFERENT", nil }) // re-derive disagrees
|
|
if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
|
|
t.Fatal("a durable-id mismatch must be refused")
|
|
}
|
|
if len(ops.formatCalls) != 0 {
|
|
t.Errorf("Format was called despite a durable-id mismatch: %v", ops.formatCalls)
|
|
}
|
|
}
|
|
|
|
// DEVICE GONE: the durable id no longer resolves → refused (device removed/replaced).
|
|
func TestWipe_ResolveFailureRefused(t *testing.T) {
|
|
ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}}
|
|
w := newWipeExec(ops,
|
|
func(string) (string, error) { return "", errors.New("no such by-id link") },
|
|
func(string) (string, error) { return durable, nil })
|
|
if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
|
|
t.Fatal("an unresolvable durable id must be refused")
|
|
}
|
|
if len(ops.formatCalls) != 0 {
|
|
t.Errorf("Format was called on an unresolvable device: %v", ops.formatCalls)
|
|
}
|
|
}
|
|
|
|
// RE-INSPECT: the resolved device is no longer data-bearing (target changed) → refused.
|
|
func TestWipe_ReinspectNonDataBearingRefused(t *testing.T) {
|
|
ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true /* blank: no fs/parts */}}
|
|
w := newWipeExec(ops,
|
|
func(string) (string, error) { return "/dev/sdb", nil },
|
|
func(string) (string, error) { return durable, nil })
|
|
if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
|
|
t.Fatal("a device that is no longer data-bearing must be refused (target changed)")
|
|
}
|
|
if len(ops.formatCalls) != 0 {
|
|
t.Errorf("Format was called on a non-target device: %v", ops.formatCalls)
|
|
}
|
|
}
|
|
|
|
// RE-INSPECT: the device did not probe cleanly → refused (fail-safe).
|
|
func TestWipe_NotProbedRefused(t *testing.T) {
|
|
ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: false}}
|
|
w := newWipeExec(ops,
|
|
func(string) (string, error) { return "/dev/sdb", nil },
|
|
func(string) (string, error) { return durable, nil })
|
|
if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil {
|
|
t.Fatal("a device that did not probe cleanly must be refused")
|
|
}
|
|
if len(ops.formatCalls) != 0 {
|
|
t.Errorf("Format was called on an unprobed device: %v", ops.formatCalls)
|
|
}
|
|
}
|
|
|
|
// A non-wipe op → ErrNoExecutor (left for the owning slice; Format not called).
|
|
func TestWipe_OtherOpIsNoExecutor(t *testing.T) {
|
|
ops := &fakeWipeOps{}
|
|
w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil })
|
|
if err := w.Execute(context.Background(), "guest_destroy", params(t, durable, "ext4")); !errors.Is(err, ErrNoExecutor) {
|
|
t.Fatalf("guest_destroy err = %v, want ErrNoExecutor", err)
|
|
}
|
|
}
|