package host import ( "context" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) func generationPayloadFixture(views []hostguard.FenceView) []hostbox.GenerationPayload { var out []hostbox.GenerationPayload for _, v := range views { in := v.Installation stamps := append([]hostguard.Captured{{Incarnation: in.Incarnation, Stamp: in.Stamp}}, v.Retired...) for _, stamp := range stamps { p := hostbox.GenerationPayload{Revision: in.InventoryRevision, Inventory: hostbox.StopInventory{Complete: true, Host: in.Host, Incarnation: stamp.Incarnation, InstallationStamp: stamp.Stamp, Box: in.Box, Enrollment: in.Enrollment, DirectoryRevision: "1"}} if !in.Empty { p.Inventory.Entries = []hostbox.StopInventoryEntry{{Kind: "route", ArtifactID: "route", ManagedRoot: "registry", RelativePath: in.Host, RowIdentity: "row", Ownership: "owner", Materialization: "materialized"}} } out = append(out, p) } } return out } func TestPlan14GenerationRegistryPayloadUnfinished(t *testing.T) { for _, kind := range []string{"route", "timer"} { for _, phase := range []string{"materialized", "intended", "predecessor", "changed-during-kernel"} { t.Run(kind+"/"+phase, func(t *testing.T) { f, disk, _ := generationLaunchFixture(t) if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } r := readGenerationLaunch(t, disk) views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } launch, err := f.journal.Read(t.Context(), f.target) if err != nil { t.Fatal(err) } if phase == "predecessor" { views[0].Retired = []hostguard.Captured{{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}} views[0].Installation.Stamp++ views[0].Installation.Empty = true } payloads := generationPayloadFixture(views) index := 0 if phase == "predecessor" { index = 1 } entry := hostbox.StopInventoryEntry{Kind: kind, ArtifactID: "registry-payload", ManagedRoot: "registry", RelativePath: f.target.Host, RowIdentity: "row", Ownership: "owner", Materialization: "materialized"} if kind == "timer" { entry.RelativePath += "/backup" } payloads[index].Inventory.Entries = []hostbox.StopInventoryEntry{entry} journal, err := hostbox.OpenLaunchJournal(filepath.Join(t.TempDir(), "payload.db")) if err != nil { t.Fatal(err) } defer journal.Close() service, err := hostbox.NewJournalService(journal) if err != nil { t.Fatal(err) } write := func() { t.Helper() for _, p := range payloads { if err := service.Inventory.WriteStopInventory(t.Context(), p.Inventory); err != nil { t.Fatal(err) } } current, err := journal.GenerationPayloads(t.Context()) if err != nil { t.Fatal(err) } for _, p := range current { if p.Inventory.InstallationStamp == views[0].Installation.Stamp { views[0].Installation.InventoryRevision = p.Revision } } } write() attack := false v := generationReconciler{read: func(ctx context.Context) (generationInventory, error) { p, err := journal.GenerationPayloads(ctx) return generationInventory{Census: rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}, Installations: views, Payloads: p, Launches: []hostbox.LaunchInventory{launch}}, err }, kernel: func(context.Context) (string, string, error) { if attack && phase == "changed-during-kernel" { payloads[index].Inventory.Entries[0].Materialization = "intended" write() } return "boot", r.Binding.Digest, nil }} source := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, reconcile: v.reconcile} ledger := &release.RootGenerations{Store: disk} accept := func() { t.Helper() pins, err := source.StagedPins(t.Context(), f.root.generations.optDir) if err != nil { t.Fatal(err) } if pinned, err := pins.Refers(r.Binding.Path); err != nil || !pinned { t.Fatalf("exact pin lost: %v", err) } if c, err := ledger.Completeness(); err != nil || !c.Complete { t.Fatalf("complete registry payload: %+v %v", c, err) } } accept() if phase == "materialized" { return } initial, _ := ledger.Completeness() attack = true if phase != "changed-during-kernel" { payloads[index].Inventory.Entries[0].Materialization = "intended" write() } if pins, err := source.StagedPins(t.Context(), f.root.generations.optDir); err == nil || pins != nil { t.Fatalf("unfinished %s payload certified: pins=%v err=%v", kind, pins, err) } if c, err := ledger.Completeness(); err != nil || c.Complete || c.Revision <= initial.Revision { t.Fatalf("unfinished payload retained certificate: %+v %v", c, err) } current, err := ledger.Read(r.Binding) if err != nil || current.Closed || current.Binding != r.Binding { t.Fatal("refusal changed the reservation") } attack = false payloads[index].Inventory.Entries[0].Materialization = "materialized" write() accept() }) } } } func TestPlan14GenerationPayloadCompleteness(t *testing.T) { for _, attack := range []string{"none", "missing", "wrong-revision", "wrong-host", "wrong-incarnation", "wrong-stamp", "wrong-box", "wrong-enrollment", "incomplete", "empty", "duplicate", "extra-predecessor", "changed-payload", "changed-revision"} { t.Run(attack, func(t *testing.T) { f, disk, _ := generationLaunchFixture(t) if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } r := readGenerationLaunch(t, disk) views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } launch, err := f.journal.Read(t.Context(), f.target) if err != nil { t.Fatal(err) } snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}, Installations: views, Launches: []hostbox.LaunchInventory{launch}, Payloads: generationPayloadFixture(views)} p := &snapshot.Payloads[0] switch attack { case "missing": snapshot.Payloads = nil case "wrong-revision": p.Revision++ case "wrong-host": p.Inventory.Host = "other.example" case "wrong-incarnation": p.Inventory.Incarnation = "other" case "wrong-stamp": p.Inventory.InstallationStamp++ case "wrong-box": p.Inventory.Box = "other" case "wrong-enrollment": p.Inventory.Enrollment = "other" case "incomplete": p.Inventory.Complete = false case "empty": p.Inventory.Entries = nil case "duplicate": snapshot.Payloads = append(snapshot.Payloads, *p) case "extra-predecessor": extra := *p extra.Inventory.InstallationStamp += 5 snapshot.Payloads = append(snapshot.Payloads, extra) } reads := 0 verifier := generationReconciler{read: func(context.Context) (generationInventory, error) { reads++ if reads > 1 { if attack == "changed-payload" { snapshot.Payloads[0].Inventory.DirectoryRevision = "2" } if attack == "changed-revision" { snapshot.Payloads[0].Revision++ } } return snapshot, nil }, kernel: func(context.Context) (string, string, error) { return "boot", r.Binding.Digest, nil }} e, err := verifier.reconcile(t.Context(), []release.GenerationReservation{r}) if (err == nil) != (attack == "none") { t.Fatalf("payload %s: evidence=%+v err=%v", attack, e, err) } if attack == "none" { snapshot.Payloads[0].Inventory.DirectoryRevision = "2" next, err := verifier.reconcile(t.Context(), []release.GenerationReservation{r}) if err != nil || next.Installations == e.Installations { t.Fatalf("changed retained payload reused evidence: %+v %v", next, err) } } }) } } func TestPlan14GenerationPayloadRetainedPredecessor(t *testing.T) { for _, attack := range []string{"none", "missing-old", "foreign-old", "extra-incarnation", "wrong-empty", "fresh-empty"} { t.Run(attack, func(t *testing.T) { v := hostguard.FenceView{Host: "tenant.example", Installation: hostguard.RootInstallation{Host: "tenant.example", Incarnation: "inc", Stamp: 5, Box: "box", Enrollment: "en", InventoryRevision: 7, Empty: true}, Retired: []hostguard.Captured{{Incarnation: "inc", Stamp: 2}}} payloads := generationPayloadFixture([]hostguard.FenceView{v}) payloads[1].Revision = 3 payloads[1].Inventory.Entries = []hostbox.StopInventoryEntry{{Kind: "path", ArtifactID: "old-state", ManagedRoot: "data", RelativePath: "instances/tenant.example/state", Ownership: "owner", Materialization: "materialized", Device: 1, Inode: 2, Type: 0100000}} switch attack { case "missing-old": payloads = payloads[:1] case "foreign-old": payloads[1].Inventory.Enrollment = "other" case "extra-incarnation": extra := payloads[1] extra.Inventory.Incarnation = "other" payloads = append(payloads, extra) case "wrong-empty": v.Installation.Empty = false case "fresh-empty": v.Retired = nil payloads = payloads[:1] } err := generationPayloadBindings(payloads, map[string]hostguard.FenceView{v.Host: v}) if (err == nil) != (attack == "none" || attack == "fresh-empty") { t.Fatalf("%s: %v", attack, err) } }) } }