package host import ( "context" "os" "path/filepath" "strings" "testing" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) func TestPlan14GenerationInventoryCompleteness(t *testing.T) { for _, attack := range []string{"none", "empty-reservations", "retired-only", "missing-installation", "missing-launch", "extra-launch", "extra-installation", "wrong-controller", "wrong-inputs", "wrong-installation", "wrong-process", "wrong-census", "unknown-migration", "incomplete-census", "kernel", "changed-snapshot"} { 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) view, err := f.root.Guard.Root.View(t.Context(), f.target.Host, hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}) 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}, Payloads: generationPayloadFixture([]hostguard.FenceView{view}), Installations: []hostguard.FenceView{view}, Launches: []hostbox.LaunchInventory{launch}} records := []release.GenerationReservation{r} switch attack { case "empty-reservations": records = nil case "retired-only": records[0].Closed = true records[0].Retirement = r.Binding.Digest case "missing-installation": snapshot.Installations = nil case "missing-launch": snapshot.Launches = nil case "extra-launch": extra := launch extra.Target.Host = "other.example" snapshot.Launches = append(snapshot.Launches, extra) case "extra-installation": extra := view extra.Host = "other.example" extra.Installation.Host = extra.Host extra.Installation.Record = provision.RecordKey(extra.Installation.Account, extra.Installation.App, extra.Host) snapshot.Installations = append(snapshot.Installations, extra) case "wrong-controller": snapshot.Launches[0].ControllerPID++ case "wrong-inputs": snapshot.Launches[0].Reservations[0].Inputs = "other" case "wrong-installation": records[0].Binding.Installation = "other" case "wrong-process": records[0].Process.PID++ case "wrong-census": snapshot.Census.Enrollment = "other" case "unknown-migration": snapshot.Census.Migration = 0 case "incomplete-census": snapshot.Census.Complete = false } reads, scans := 0, 0 verifier := generationReconciler{read: func(context.Context) (generationInventory, error) { reads++ out := snapshot if attack == "changed-snapshot" && reads > 1 { out.Census.Revision++ } return out, nil }, kernel: func(context.Context) (string, string, error) { scans++ if attack == "kernel" { return "", "", os.ErrPermission } return "boot-now", r.Binding.Digest, nil }} evidence, err := verifier.reconcile(t.Context(), records) if attack == "none" { if err != nil || scans != 1 || reads != 2 || evidence.Box != f.target.Box || evidence.Installations == "" || evidence.Launches == "" { t.Fatalf("positive: %+v %v reads=%d scans=%d", evidence, err, reads, scans) } } else if err == nil { t.Fatalf("%s certified: %+v", attack, evidence) } }) } } func TestPlan14GenerationInventoryEmptyNeedsIndependentSources(t *testing.T) { snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}} for _, missing := range []string{"none", "inventory", "kernel", "census"} { t.Run(missing, func(t *testing.T) { v := generationReconciler{read: func(context.Context) (generationInventory, error) { if missing == "census" { return generationInventory{}, nil } return snapshot, nil }, kernel: func(context.Context) (string, string, error) { return "boot", strings.Repeat("a", 64), nil }} if missing == "inventory" { v.read = nil } if missing == "kernel" { v.kernel = nil } _, err := v.reconcile(t.Context(), nil) if (err == nil) != (missing == "none") { t.Fatalf("empty: %v", err) } }) } } func TestPlan14GenerationInventoryPinConsumer(t *testing.T) { for _, attack := range []string{"none", "omitted-reservation", "kernel", "lost-census", "missing-payload", "payload-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) view, err := f.root.Guard.Root.View(t.Context(), f.target.Host, hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}) if err != nil { t.Fatal(err) } launch, err := f.journal.Read(t.Context(), f.target) if err != nil { t.Fatal(err) } reads := 0 v := generationReconciler{read: func(context.Context) (generationInventory, error) { reads++ c := rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true} if attack == "lost-census" { c.Complete = false } payloads := generationPayloadFixture([]hostguard.FenceView{view}) if attack == "missing-payload" { payloads = nil } if attack == "payload-revision" { payloads[0].Revision++ } return generationInventory{Census: c, Payloads: payloads, Installations: []hostguard.FenceView{view}, Launches: []hostbox.LaunchInventory{launch}}, nil }, kernel: func(context.Context) (string, string, error) { if attack == "kernel" { return "", "", os.ErrPermission } return "boot", r.Binding.Digest, nil }} if attack == "omitted-reservation" { names, err := disk.Names() if err != nil { t.Fatal(err) } for _, name := range names { if err := disk.Remove(name); err != nil { t.Fatal(err) } } } s := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, reconcile: v.reconcile} pins, err := s.StagedPins(t.Context(), f.root.generations.optDir) if attack != "none" { if err == nil { t.Fatalf("unsafe snapshot: %+v", pins) } return } if err != nil || reads != 2 { t.Fatalf("pins: %v reads=%d", err, reads) } if pinned, err := pins.Refers(r.Binding.Path); err != nil || !pinned { t.Fatalf("missing exact pin: %v", err) } c, err := (&release.RootGenerations{Store: disk}).Completeness() if err != nil || !c.Complete { t.Fatalf("certificate: %+v %v", c, err) } }) } s, ok := productionRootGenerationPins().(generationPinSource) if !ok || s.reconcile == nil { t.Fatal("production reconciliation not connected") } } func TestPlan14GenerationInventoryLifecycle(t *testing.T) { for _, mode := range []string{"pending", "pending-wrong-controller", "retired", "retained-rebind", "unrecognized-predecessor"} { t.Run(mode, func(t *testing.T) { f, disk, _ := generationLaunchFixture(t) prior, err := f.journal.Read(t.Context(), f.target) if err != nil { t.Fatal(err) } journal, err := hostbox.OpenLaunchJournal(filepath.Join(t.TempDir(), "launch.db")) if err != nil { t.Fatal(err) } defer journal.Close() if err := journal.Prelaunch(t.Context(), prior); err != nil { t.Fatal(err) } f.root.Journal, f.journal = journal, journal pending := strings.HasPrefix(mode, "pending") if pending { if _, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req); err != nil { t.Fatal(err) } } else { if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil { t.Fatal(err) } if err := journal.CloseWriters(t.Context(), f.target); err != nil { t.Fatal(err) } } r := readGenerationLaunch(t, disk) if mode == "retired" { v := generationRetirement{guard: f.root.Guard, journal: journal, kernel: func(context.Context, release.GenerationReservation) (string, error) { return r.Binding.Digest, nil }} if err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r); err != nil { t.Fatal(err) } } if mode == "retained-rebind" || mode == "unrecognized-predecessor" { captured := hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp} in, err := f.root.Guard.Root.Installation(t.Context(), f.target.Host, captured) if err != nil { t.Fatal(err) } next := in next.Stamp += 3 next.InventoryRevision++ f.claims.set(func(c *hostclaim.Claim) { c.Holder.Stamp = next.Stamp }) if err := f.root.Guard.RebindRootInventory(t.Context(), captured, in.InventoryRevision, next, []byte("verified-rebind"), func(*hostclaim.Claim) error { return nil }, func() error { return nil }); err != nil { t.Fatal(err) } } v := generationReconciler{read: func(ctx context.Context) (generationInventory, error) { views, err := f.root.Guard.Root.GenerationInstallations(ctx) if err != nil { return generationInventory{}, err } launches, err := journal.GenerationLaunches(ctx) if err != nil { return generationInventory{}, err } if mode == "unrecognized-predecessor" { views[0].Retired = nil } if mode == "pending-wrong-controller" { launches[0].ControllerPID++ } return generationInventory{Census: rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}, Payloads: generationPayloadFixture(views), Installations: views, Launches: launches}, nil }, kernel: func(context.Context) (string, string, error) { return "boot", r.Binding.Digest, nil }} source := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, reconcile: v.reconcile} pins, err := source.StagedPins(t.Context(), f.root.generations.optDir) if mode == "pending-wrong-controller" || mode == "unrecognized-predecessor" { if err == nil { t.Fatal("unbound generation certified") } return } if err != nil { t.Fatal(err) } pinned, err := pins.Refers(r.Binding.Path) if err != nil || pinned != (mode != "retired") { t.Fatalf("exact lifecycle pin=%v: %v", pinned, err) } }) } }