//go:build unix package hostguard import ( "context" "errors" "testing" "time" ) func TestPlan14MigrationDenyCoversRootAndAgent(t *testing.T) { f, target, captured := fenceFixture(t) root := NewRootAuthority(&rootTestStore{dir: t.TempDir()}) f.g.Root = root f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "app" in := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "app", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1} if err := f.g.EstablishRoot(t.Context(), in, allow); err != nil { t.Fatal(err) } if err := f.g.With(t.Context(), target.Host, captured, allow, func() error { return nil }); err != nil { t.Fatal("canonical", err) } if err := root.RetainMigrationDeny(t.Context(), in, target.Session); err != nil { t.Fatal(err) } if err := f.g.With(t.Context(), target.Host, captured, allow, func() error { t.Fatal("old session ran"); return nil }); !errors.Is(err, ErrFenced) { t.Fatal(err) } if err := f.g.EstablishRoot(t.Context(), in, allow); !errors.Is(err, ErrFenced) { t.Fatal("re-establishment bypass", err) } view, err := root.View(t.Context(), target.Host, captured) if err != nil { t.Fatal(err) } if err := view.admit(target.Host, captured, target.Session); !errors.Is(err, ErrFenced) { t.Fatal("agent view bypass", err) } target, err = f.g.BeginFence(t.Context(), target) if err != nil { t.Fatal(err) } if err = f.g.CompleteFence(t.Context(), target, []byte("verified predecessor"), time.Now(), func(context.Context, FenceTarget, []byte, time.Time) error { return nil }); err != nil { t.Fatal(err) } view, err = root.View(t.Context(), target.Host, captured) if err != nil { t.Fatal(err) } f.obs.Claim.Holder.Session++ if err := f.g.With(t.Context(), target.Host, captured, allow, func() error { return nil }); err != nil { t.Fatal("fresh authoritative session denied", err) } if err := view.admit(target.Host, captured, target.Session+1); err != nil { t.Fatal("new session view", err) } if err := root.RetainMigrationDeny(t.Context(), in, target.Session-1); err != nil { t.Fatal(err) } again, err := root.View(t.Context(), target.Host, captured) if err != nil || again.MigrationDenies[0].Session != target.Session { t.Fatal("deny regressed", again, err) } } func TestPlan14MigrationReadmissionRequiresCompletedPredecessor(t *testing.T) { f, target, captured := fenceFixture(t) root := NewRootAuthority(&rootTestStore{dir: t.TempDir()}) f.g.Root = root f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "app" in := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "app", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1} if err := root.RetainMigrationDeny(t.Context(), in, target.Session); err != nil { t.Fatal(err) } f.obs.Claim.Holder.Session++ if f.g.With(t.Context(), target.Host, captured, allow, func() error { return nil }) == nil { t.Fatal("session advance bypassed predecessor stop") } f.obs.Claim.Holder.Session-- target, err := f.g.BeginFence(t.Context(), target) if err != nil { t.Fatal(err) } if err := f.g.CompleteFence(t.Context(), target, []byte("verified exact root stop"), time.Now(), func(context.Context, FenceTarget, []byte, time.Time) error { return nil }); err != nil { t.Fatal(err) } f.obs.Claim.Holder.Session++ effects := 0 if err := f.g.With(t.Context(), target.Host, captured, allow, func() error { effects++; return nil }); err != nil || effects != 1 { t.Fatal("completed predecessor", err, effects) } f.obs.Claim.Holder.Session-- if f.g.With(t.Context(), target.Host, captured, allow, func() error { t.Fatal("old session ran"); return nil }) == nil { t.Fatal("deny lost") } } func TestPlan14MigrationInventoryRevisionAdvance(t *testing.T) { f, target, captured := fenceFixture(t) root := NewRootAuthority(&rootTestStore{dir: t.TempDir()}) f.g.Root = root f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "app" in := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "app", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1} if err := root.RetainMigrationDeny(t.Context(), in, target.Session); err != nil { t.Fatal(err) } artifacts := InstallationArtifacts{Schema: 1, Timers: "refresh"} if root.CompleteMigrationInventory(t.Context(), target, 4, artifacts) == nil { t.Fatal("revision without stopped receipt") } var err error target, err = f.g.BeginFence(t.Context(), target) if err != nil { t.Fatal(err) } if err := f.g.CompleteFence(t.Context(), target, []byte("verified predecessor"), time.Now(), func(context.Context, FenceTarget, []byte, time.Time) error { return nil }); err != nil { t.Fatal(err) } if err := root.CompleteMigrationInventory(t.Context(), target, 4, artifacts); err != nil { t.Fatal(err) } for _, change := range [][2]uint64{{0, 1}, {3, 5}, {4, 6}, {5, 6}, {^uint64(0), 0}} { if root.CompleteMigrationInventory(t.Context(), target, change[1], artifacts, change[0]) == nil { t.Fatal("unbound revision advance", change) } } if err := root.CompleteMigrationInventory(t.Context(), target, 5, artifacts, 4); err != nil { t.Fatal("exact successor", err) } if err := root.CompleteMigrationInventory(t.Context(), target, 5, artifacts, 4); err != nil { t.Fatal("retry", err) } if root.CompleteMigrationInventory(t.Context(), target, 4, artifacts) == nil { t.Fatal("revision regressed") } view, err := root.View(t.Context(), target.Host, captured) if err != nil || view.Installation.InventoryRevision != 5 || len(view.MigrationDenies) != 1 || view.MigrationDenies[0].Session != target.Session { t.Fatal(view, err) } }