c17cfde236
internal/storage: mkfs executor (Format, device-pinned, narrow FELHOM_FORMAT sudoers) + data-bearing device inspection (InspectDevice/DeviceProbe via blkid+lsblk; conservative — ambiguous=data-bearing). internal/localapi: /disks (+ data-bearing flag), /disks/assign (EnsureMount), /disks/eject (Unmount + dependent guests), /disks/format. SECURITY CENTERPIECE: the agent inspects the device itself; data-bearing format -> ClassStorageWipe gate -> pending_signature refused; the caller's claim is never trusted. Additive (no controller change yet). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
186 lines
5.6 KiB
Go
186 lines
5.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// scriptedRunner returns canned stdout/stderr/err per command name (last arg = device).
|
|
type scriptedRunner struct {
|
|
calls [][]string
|
|
outputs map[string][]byte // keyed by binary basename
|
|
errs map[string]error
|
|
}
|
|
|
|
func (r *scriptedRunner) Run(_ context.Context, name string, args ...string) ([]byte, []byte, error) {
|
|
r.calls = append(r.calls, append([]string{name}, args...))
|
|
base := name
|
|
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
|
base = name[i+1:]
|
|
}
|
|
return r.outputs[base], nil, r.errs[base]
|
|
}
|
|
|
|
func (r *scriptedRunner) ran(substr string) bool {
|
|
for _, c := range r.calls {
|
|
if strings.Contains(strings.Join(c, " "), substr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func newSudo(r *scriptedRunner) *SudoHostOps {
|
|
return NewSudoHostOps(SudoHostOpsConfig{Runner: r})
|
|
}
|
|
|
|
// ---- validators -------------------------------------------------------------------------
|
|
|
|
func TestValidateBlockDevice(t *testing.T) {
|
|
ok := []string{"/dev/sdb", "/dev/sdb1", "/dev/nvme0n1", "/dev/nvme0n1p2", "/dev/vdb3"}
|
|
for _, d := range ok {
|
|
if err := ValidateBlockDevice(d); err != nil {
|
|
t.Errorf("expected %q valid: %v", d, err)
|
|
}
|
|
}
|
|
bad := []string{"/dev/disk/by-uuid/x", "/dev/../etc/passwd", "/dev/sdb; rm -rf /", "/etc/shadow", "/dev/mapper/x", "sdb", ""}
|
|
for _, d := range bad {
|
|
if err := ValidateBlockDevice(d); err == nil {
|
|
t.Errorf("expected %q rejected", d)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateFSType(t *testing.T) {
|
|
for _, f := range []string{"ext4", "xfs"} {
|
|
if err := ValidateFSType(f); err != nil {
|
|
t.Errorf("expected %q valid", f)
|
|
}
|
|
}
|
|
for _, f := range []string{"ntfs", "vfat", "ext4 ", "", "ext4;ls"} {
|
|
if err := ValidateFSType(f); err == nil {
|
|
t.Errorf("expected %q rejected", f)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- InspectDevice (data-bearing detection) ---------------------------------------------
|
|
|
|
func TestInspect_Blank(t *testing.T) {
|
|
// blkid finds nothing (empty output); lsblk reads cleanly and shows a bare disk.
|
|
r := &scriptedRunner{
|
|
outputs: map[string][]byte{
|
|
"blkid": nil,
|
|
"lsblk": []byte(`{"blockdevices":[{"name":"sdb","fstype":null,"pttype":null,"mountpoint":null}]}`),
|
|
},
|
|
errs: map[string]error{"blkid": errors.New("exit status 2")}, // blkid exits non-zero on blank
|
|
}
|
|
p, err := newSudo(r).InspectDevice(context.Background(), "/dev/sdb")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !p.Probed {
|
|
t.Fatal("expected a clean probe (lsblk read cleanly)")
|
|
}
|
|
if p.DataBearing() {
|
|
t.Fatalf("blank device classified data-bearing: %+v", p)
|
|
}
|
|
}
|
|
|
|
func TestInspect_HasFilesystem(t *testing.T) {
|
|
r := &scriptedRunner{
|
|
outputs: map[string][]byte{
|
|
"blkid": []byte("DEVNAME=/dev/sdb\nTYPE=ext4\nUSAGE=filesystem\n"),
|
|
"lsblk": []byte(`{"blockdevices":[{"name":"sdb","fstype":"ext4","pttype":null,"mountpoint":null}]}`),
|
|
},
|
|
}
|
|
p, _ := newSudo(r).InspectDevice(context.Background(), "/dev/sdb")
|
|
if !p.DataBearing() || !p.HasFilesystem || p.FSType != "ext4" {
|
|
t.Fatalf("filesystem not detected: %+v", p)
|
|
}
|
|
}
|
|
|
|
func TestInspect_HasPartitionTable(t *testing.T) {
|
|
r := &scriptedRunner{
|
|
outputs: map[string][]byte{
|
|
"blkid": []byte("DEVNAME=/dev/sdb\nPTTYPE=gpt\n"),
|
|
"lsblk": []byte(`{"blockdevices":[{"name":"sdb","pttype":"gpt","children":[{"name":"sdb1","fstype":"ext4"}]}]}`),
|
|
},
|
|
}
|
|
p, _ := newSudo(r).InspectDevice(context.Background(), "/dev/sdb")
|
|
if !p.DataBearing() || !p.HasPartitionTable || !p.HasPartitions {
|
|
t.Fatalf("partition table/children not detected: %+v", p)
|
|
}
|
|
}
|
|
|
|
func TestInspect_Mounted(t *testing.T) {
|
|
r := &scriptedRunner{
|
|
outputs: map[string][]byte{
|
|
"blkid": []byte("TYPE=xfs\n"),
|
|
"lsblk": []byte(`{"blockdevices":[{"name":"sdb","fstype":"xfs","mountpoint":"/mnt/data"}]}`),
|
|
},
|
|
}
|
|
p, _ := newSudo(r).InspectDevice(context.Background(), "/dev/sdb")
|
|
if !p.Mounted || !p.DataBearing() {
|
|
t.Fatalf("mounted not detected: %+v", p)
|
|
}
|
|
}
|
|
|
|
// A probe that fails to read cleanly must be conservative (data-bearing).
|
|
func TestInspect_FailedProbe_FailSafe(t *testing.T) {
|
|
r := &scriptedRunner{
|
|
outputs: map[string][]byte{"blkid": nil, "lsblk": nil},
|
|
errs: map[string]error{"blkid": errors.New("blkid broke"), "lsblk": errors.New("lsblk broke")},
|
|
}
|
|
p, _ := newSudo(r).InspectDevice(context.Background(), "/dev/sdb")
|
|
if p.Probed {
|
|
t.Fatal("a broken probe must not be 'Probed'")
|
|
}
|
|
if !p.DataBearing() {
|
|
t.Fatal("a broken probe must be treated as data-bearing (fail-safe)")
|
|
}
|
|
}
|
|
|
|
func TestInspect_RejectsBadDevice(t *testing.T) {
|
|
if _, err := newSudo(&scriptedRunner{}).InspectDevice(context.Background(), "/dev/../etc"); err == nil {
|
|
t.Fatal("expected a bad device to be rejected before any exec")
|
|
}
|
|
}
|
|
|
|
// ---- Format (mkfs) ----------------------------------------------------------------------
|
|
|
|
func TestFormat_Ext4(t *testing.T) {
|
|
r := &scriptedRunner{}
|
|
if err := newSudo(r).Format(context.Background(), "/dev/sdb", "ext4"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !r.ran("mkfs.ext4 -F /dev/sdb") {
|
|
t.Fatalf("mkfs.ext4 not invoked correctly: %v", r.calls)
|
|
}
|
|
}
|
|
|
|
func TestFormat_Xfs(t *testing.T) {
|
|
r := &scriptedRunner{}
|
|
if err := newSudo(r).Format(context.Background(), "/dev/nvme0n1p1", "xfs"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !r.ran("mkfs.xfs -f /dev/nvme0n1p1") {
|
|
t.Fatalf("mkfs.xfs not invoked correctly: %v", r.calls)
|
|
}
|
|
}
|
|
|
|
func TestFormat_RejectsBadArgs(t *testing.T) {
|
|
r := &scriptedRunner{}
|
|
if err := newSudo(r).Format(context.Background(), "/dev/disk/by-uuid/x", "ext4"); err == nil {
|
|
t.Fatal("expected bad device rejected")
|
|
}
|
|
if err := newSudo(r).Format(context.Background(), "/dev/sdb", "ntfs"); err == nil {
|
|
t.Fatal("expected bad fstype rejected")
|
|
}
|
|
if len(r.calls) != 0 {
|
|
t.Fatalf("mkfs ran despite invalid input: %v", r.calls)
|
|
}
|
|
}
|