v0.101.0 — R-82: a leaked restore-test scratch can no longer auto-start

CORRECTION: I earlier reported that the restore-test would boot a scratch guest
with the live guest's MAC/static island IP/hostname and break the control
plane. That was WRONG — RunRestoreTest step 2 link-downs EVERY interface
(withLinkDown, unit-tested) before the guest is ever started. The design
already handled it.

The real, narrower hazard: a restore that fails BEFORE step 2 (what the v0.100.0
wait bug caused) leaves a scratch holding the SOURCE guest's config verbatim,
including onboot:1. If teardown also fails (403 missing VM.Allocate — PVE
associates the pool only at restore completion), a host reboot would start that
leaked clone alongside the original with NICs up.

- proxmox.RestoreLXCOptions.ConfigOverrides: guest-config params applied AT
  RESTORE TIME.
- The restore-test passes onboot=0 — at restore time, not after, because
  'after' is exactly the path that leaks.

NOT changed: the link-down step (already correct, the primary defence); the
agent's Proxmox privileges (widening VM.Allocate to /vms would remove the
accidental guard that stopped a destructive mid-restore teardown).

restore_test_cadence_seconds was set to -1 on demo-felhom under the mistaken
reading; re-enabled.

Red-proof observed; full suite green (29 packages).
This commit is contained in:
Claude Code
2026-07-26 16:49:40 +02:00
parent a7421b09c7
commit 0fabc15896
4 changed files with 80 additions and 0 deletions
+6
View File
@@ -231,6 +231,12 @@ func (e *Engine) runScratchTest(ctx context.Context, vmid int, spec RestoreTestS
// Pool=DefaultPool so the scratch guest is created INTO the felhom pool — else a pool-scoped
// token 403s on the scratch guest's config/start/destroy (SPIKE residual #2).
VMID: vmid, Archive: spec.Archive, Storage: spec.RestoreStorage, MountOverrides: mountOverrides, Pool: DefaultPool,
// onboot=0 from the instant the guest exists. Step 2 below link-downs every NIC before the
// guest is ever started, so the NORMAL path cannot conflict with the live source. This
// covers the ABNORMAL path: a restore that fails before step 2 (e.g. the wait expiring) can
// leave a scratch carrying the source's `onboot: 1` plus its MAC/static island IP/hostname —
// which a host reboot would then start alongside the original. Observed live 2026-07-26.
ConfigOverrides: map[string]string{"onboot": "0"},
})
if err != nil {
if pveAlreadyExists(err) {