slice 8C Phase A: agent disk endpoints + data-bearing classifier gate + mkfs (v0.12.0)
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>
This commit is contained in:
@@ -40,7 +40,7 @@ import (
|
||||
|
||||
// version is the agent version. Overridable at build time with
|
||||
// -ldflags "-X main.version=<v>"; defaults to the in-repo CHANGELOG version.
|
||||
var version = "0.11.0"
|
||||
var version = "0.12.0"
|
||||
|
||||
func main() {
|
||||
var (
|
||||
@@ -337,7 +337,7 @@ func runDaemon(cfg config.Config, logger *slog.Logger) int {
|
||||
// host still reports/reconciles). The leaf is generated+persisted once so its pin is stable.
|
||||
localServers := 0
|
||||
var localTokens *localapi.TokenStore
|
||||
localSrv := buildLocalAPIServer(cfg, px, backupStore, observer, logger, &localTokens)
|
||||
localSrv := buildLocalAPIServer(cfg, px, backupStore, observer, hostOps, gate, logger, &localTokens)
|
||||
if localTokens != nil {
|
||||
defer localTokens.Close()
|
||||
}
|
||||
@@ -462,7 +462,7 @@ func buildRestoreTestScheduler(cfg config.Config, px *proxmox.Client, engine *re
|
||||
// leaf (stable fingerprint). Any failure DISABLES the server (returns nil) WITHOUT crashing the
|
||||
// daemon — the host still reports/reconciles; only the controller channel is unavailable until
|
||||
// fixed. The opened token store is returned via outTokens so the caller can Close it.
|
||||
func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.Store, observer *storage.Observer, logger *slog.Logger, outTokens **localapi.TokenStore) *localapi.Server {
|
||||
func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.Store, observer *storage.Observer, hostOps storage.HostOps, gate *reconcile.Gate, logger *slog.Logger, outTokens **localapi.TokenStore) *localapi.Server {
|
||||
if !cfg.LocalAPI.Enabled() {
|
||||
return nil
|
||||
}
|
||||
@@ -493,7 +493,11 @@ func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.St
|
||||
Storage: observer,
|
||||
Tokens: tokens,
|
||||
BackupCadence: cfg.Backup.BackupCadence(),
|
||||
Logger: logger,
|
||||
// Disk management (slice 8C): the privileged host surface + the data-bearing wipe gate.
|
||||
Disks: hostOps,
|
||||
DiskGate: storageGateAdapter{gate: gate, hostID: cfg.Hub.HostID},
|
||||
Guests2: px,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Warn("daemon: local-api disabled (server build)", "err", err)
|
||||
@@ -502,6 +506,23 @@ func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.St
|
||||
return srv
|
||||
}
|
||||
|
||||
// storageGateAdapter bridges the local-API disk-format path to the slice-4 reversibility gate
|
||||
// (8C): a data-bearing format is a ClassStorageWipe destructive op, so with no operator signature
|
||||
// the gate returns pending_signature and the agent refuses. The signed completion is slice 10.
|
||||
type storageGateAdapter struct {
|
||||
gate *reconcile.Gate
|
||||
hostID string
|
||||
}
|
||||
|
||||
func (a storageGateAdapter) AuthorizeWipe(device string) (bool, string) {
|
||||
params, _ := json.Marshal(map[string]string{"device": device, "op": "format"})
|
||||
dec := a.gate.Authorize(
|
||||
reconcile.IntentForStorageDestructive(reconcile.ClassStorageWipe, a.hostID, device, params, reconcile.SourceOneShotJob),
|
||||
nil, // no operator signature in 8C → pending_signature
|
||||
)
|
||||
return dec.Allowed, string(dec.Reason)
|
||||
}
|
||||
|
||||
// reconcileJournalPath chooses the op-journal path: a `journal.log` sibling of the
|
||||
// configured nonce store (both are durable agent state), falling back to the standard
|
||||
// host state dir when the nonce store is unset.
|
||||
|
||||
Reference in New Issue
Block a user