agent v0.86.0: DR-tier-by-default — capability inactive state (GatedBy/GateActive, pbsdr gate via DRConfigured) + F-3 root-run provision parent ownership
Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN
This commit is contained in:
@@ -52,6 +52,9 @@ type BackHalf struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// geteuid is the effective-uid seam (F-3): tests fake a root run without being root.
|
||||
var geteuid = os.Geteuid
|
||||
|
||||
// NewBackHalf builds the back-half. stateDir defaults to /var/lib/felhom-agent when empty.
|
||||
func NewBackHalf(tokens TokenMinter, runner proxmox.Runner, stateDir string, logger *slog.Logger) *BackHalf {
|
||||
if stateDir == "" {
|
||||
@@ -135,6 +138,23 @@ func (b *BackHalf) Provision(ctx context.Context, in Input) (Result, error) {
|
||||
if err := os.MkdirAll(hostDir, 0o700); err != nil {
|
||||
return Result{}, fmt.Errorf("provision: config dir: %w", err)
|
||||
}
|
||||
|
||||
// 3b. F-3 (DRILL-day0-vm-2026-07-12): a ROOT-run provision (the Day-0 one-shot) leaves the
|
||||
// just-created guests/ + guests/<vmid>/ parents root:root 0700 inside the agent-owned
|
||||
// state dir — the non-root daemon's lanresolver then can't traverse them ("permission
|
||||
// denied"). Own the two PARENTS to whatever owns the state dir (chown --reference; the
|
||||
// installer made that the agent user). NON-recursive on purpose: only the bootstrap leaf
|
||||
// below belongs to the mapped guest-root. A daemon-run provision creates them as the
|
||||
// agent user already — euid≠0 skips (and needs no sudoers vector for this shape).
|
||||
if geteuid() == 0 {
|
||||
guestsDir := filepath.Join(b.stateDir, "guests")
|
||||
vmidDir := filepath.Join(guestsDir, strconv.Itoa(in.VMID))
|
||||
if err := b.run(ctx, "chown", "--reference="+b.stateDir, guestsDir, vmidDir); err != nil {
|
||||
// Warn-only: the drill's manual fix is a one-liner and lanresolver degrades visibly.
|
||||
b.logger.Warn("provision: could not own guests parent dirs to the state-dir owner (F-3)",
|
||||
"err", err)
|
||||
}
|
||||
}
|
||||
bootPath := filepath.Join(hostDir, bootstrapFile)
|
||||
if err := os.WriteFile(bootPath, rendered, 0o600); err != nil {
|
||||
return Result{}, fmt.Errorf("provision: write bootstrap: %w", err)
|
||||
|
||||
@@ -157,6 +157,45 @@ func TestProvision_WritesChownsAndAttaches(t *testing.T) {
|
||||
|
||||
// F3: the provisioned customer guest must be set onboot:1 so it auto-starts after a host
|
||||
// reboot/power-cut (the golden bakes onboot:0 as a template). Assert the exact pct invocation.
|
||||
// F-3 (DRILL-day0-vm-2026-07-12): a ROOT-run provision must chown the guests/ + guests/<vmid>/
|
||||
// PARENT dirs to the state-dir's owner (chown --reference, NON-recursive — the bootstrap leaf
|
||||
// stays the mapped guest-root's). A non-root run must NOT issue it (the dirs are already
|
||||
// agent-created). Companion red-proof: remove the geteuid()==0 chown block in Provision → the
|
||||
// root case fails (no such invocation recorded); the non-root case alone stays green.
|
||||
func TestProvision_RootRunOwnsGuestsParents(t *testing.T) {
|
||||
orig := geteuid
|
||||
defer func() { geteuid = orig }()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
euid int
|
||||
want bool
|
||||
}{
|
||||
{"root run issues the parent chown", 0, true},
|
||||
{"non-root run does not", 1001, false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
geteuid = func() int { return tc.euid }
|
||||
dir := t.TempDir()
|
||||
runner := &recRunner{}
|
||||
bh := NewBackHalf(&mintMinter{token: "T"}, runner, dir, testLogger())
|
||||
if _, err := bh.Provision(context.Background(), newInput()); err != nil {
|
||||
t.Fatalf("provision: %v", err)
|
||||
}
|
||||
guestsDir := filepath.Join(dir, "guests")
|
||||
vmidDir := filepath.Join(guestsDir, "8200")
|
||||
got := runner.hasExact("chown", "--reference="+dir, guestsDir, vmidDir)
|
||||
if got != tc.want {
|
||||
t.Fatalf("parent chown issued=%v want=%v; recorded: %v", got, tc.want, runner.cmds)
|
||||
}
|
||||
// Never recursive — the guest-root bootstrap subtree must stay untouched.
|
||||
if runner.hasExact("chown", "-R", "--reference="+dir, guestsDir, vmidDir) {
|
||||
t.Fatal("parent chown ran recursively")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Companion red-proof: removing the `b.run(... -onboot 1)` call in Provision makes this FAIL
|
||||
// (no such invocation recorded) — re-applying the call turns it green.
|
||||
func TestProvision_SetsOnbootOne(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user