package host import ( "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" ) func TestPlan14RootLaunchRefusesReplacedAgentFence(t *testing.T) { for _, attack := range []string{"honest", "delete", "rename", "replace lock", "forge", "receipt replay"} { t.Run(attack, func(t *testing.T) { f := newLaunchFixture(t, servingOp()) f.claims.set(func(c *hostclaim.Claim) { c.Holder.Account = "bab" c.Holder.App = "app" c.Holder.Admissions = []hostclaim.AdmissionRecord{{Box: f.target.Box, Region: f.target.Region, Session: f.target.Session, Generation: c.Holder.Generation}} }) guard := f.root.Guard guard.Root = hostguard.NewRootAuthority(&rollbackDisk{dir: t.TempDir()}) in := hostguard.RootInstallation{Host: f.target.Host, Account: "bab", App: "app", Record: "record", Incarnation: f.target.Incarnation, Stamp: f.target.Stamp, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1} if err := guard.EstablishRoot(t.Context(), in, func(*hostclaim.Claim) error { return nil }); err != nil { t.Fatal(err) } f.root.Guard = guard if attack == "honest" { if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } if n, _ := f.units.counts(); n != 1 { t.Fatalf("honest launch count: %d", n) } return } if attack == "receipt replay" { if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } } target := f.target target.FenceID = "root-fence" if _, err := guard.BeginFence(t.Context(), target); err != nil { t.Fatal(err) } path := filepath.Join(guard.DataDir, "hostguard", target.Host+".json") switch attack { case "delete": if err := os.Remove(path); err != nil { t.Fatal(err) } case "rename": if err := os.Rename(filepath.Dir(path), filepath.Dir(path)+".old"); err != nil { t.Fatal(err) } case "replace lock": if err := os.Rename(guard.LockDir, guard.LockDir+".old"); err != nil { t.Fatal(err) } if err := os.Mkdir(guard.LockDir, 0700); err != nil { t.Fatal(err) } case "forge": if err := os.WriteFile(path, []byte(`{"v":1,"host":"shop.example","incarnation":"inc-1","active_stamp":11}`), 0600); err != nil { t.Fatal(err) } } before, _ := f.units.counts() if _, err := f.root.Serve(t.Context(), f.peer, f.req); err == nil { t.Fatal("root launch bypassed fence") } if n, _ := f.units.counts(); n != before { t.Fatal("fenced launch started a unit") } pending, err := guard.PendingFences(t.Context()) if err != nil || len(pending) != 1 || pending[0] != target { t.Fatalf("lost root recovery target: %+v %v", pending, err) } }) } }