package host import ( "context" "os" "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 TestPlan14GenerationEmptyInstallationRequiresCreation(t *testing.T) { plan14GenerationCreation(t, false) } func TestPlan14GenerationLocalRecoveryCreation(t *testing.T) { plan14GenerationCreation(t, true) } func plan14GenerationCreation(t *testing.T, local bool) { for _, attack := range []string{"none", "missing", "erased", "corrupt", "consumed", "wrong-installation", "wrong-host", "visible-sync", "changed-during-kernel"} { t.Run(attack, func(t *testing.T) { f, disk, rec := generationLaunchFixture(t) rec.Schema = 1 plan14PutRecord(t, f.root.generations.store, rec) views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } views[0].Installation.Empty = true in := views[0].Installation snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: in.Box, Enrollment: in.Enrollment, Migration: in.Migration, Revision: 1, LaunchRevision: 1, Complete: true}, Installations: views, Payloads: generationPayloadFixture(views)} historyDisk := &generationDisk{rollbackDisk: &rollbackDisk{dir: t.TempDir()}} history := &release.RootHistory{Store: historyDisk} binding := release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App} create := func() { t.Helper() if err := history.RecordCreation(binding, rootHistoryInstallation(in)); err != nil { t.Fatal(err) } } create() active := false fresh := func(in hostguard.RootInstallation) error { ok, err := history.FreshInstallation(release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App}, rootHistoryInstallation(in)) if err != nil { return err } if !ok { return os.ErrPermission } return nil } v := generationReconciler{read: func(ctx context.Context) (generationInventory, error) { if local { return generationLocalObligations(ctx, snapshot, fresh) } return generationRequiredInventory(ctx, f.root.generations.store, snapshot, fresh) }, kernel: func(context.Context) (string, string, error) { if active && attack == "changed-during-kernel" { if err := history.ConsumeCreation(binding); err != nil { t.Fatal(err) } } return "boot", generationInventoryDigest("kernel"), 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 || len(pins) != 0 { t.Fatalf("new installation: %v %v", pins, err) } if c, err := ledger.Completeness(); err != nil || !c.Complete { t.Fatalf("creation not certified: %+v %v", c, err) } } accept() if attack == "none" { return } before, _ := ledger.Completeness() names, err := historyDisk.Names() if err != nil || len(names) != 1 { t.Fatalf("history names: %v %v", names, err) } raw, err := historyDisk.Read(names[0]) if err != nil { t.Fatal(err) } switch attack { case "visible-sync": historyDisk.fail = 2 case "missing": fresh = nil case "erased": if err := historyDisk.Remove(names[0]); err != nil { t.Fatal(err) } case "corrupt": if err := historyDisk.Write(names[0], []byte("{")); err != nil { t.Fatal(err) } case "consumed": if err := history.ConsumeCreation(binding); err != nil { t.Fatal(err) } case "wrong-installation", "wrong-host": if err := historyDisk.Remove(names[0]); err != nil { t.Fatal(err) } other, identity := binding, rootHistoryInstallation(in) if attack == "wrong-host" { other.Host = "other.example" } else { identity = "other-installation" } if err := history.RecordCreation(other, identity); err != nil { t.Fatal(err) } } active = true if pins, err := source.StagedPins(t.Context(), f.root.generations.optDir); err == nil || pins != nil { t.Fatalf("unknown creation certified: %v %v", pins, err) } if c, err := ledger.Completeness(); err != nil || c.Complete || c.Revision <= before.Revision { t.Fatalf("creation refusal retained certificate: %+v %v", c, err) } if attack == "visible-sync" { if pins, err := source.StagedPins(t.Context(), f.root.generations.optDir); err == nil || pins != nil { t.Fatalf("visible unsynced creation accepted on retry: %v %v", pins, err) } } if err := historyDisk.Write(names[0], raw); err != nil { t.Fatal(err) } active = false if attack != "missing" { accept() } }) } } func TestPlan14GenerationCreationFromRegistration(t *testing.T) { f, root, q := plan14InstallRegistration(t, hostclaim.KindServing, "") history := &release.RootHistory{Store: &rollbackDisk{dir: t.TempDir()}} root.history = func(ctx context.Context, fn func(*release.RootHistory) error) error { return fn(history) } if _, err := root.commitRootInventory(t.Context(), q); err != nil { t.Fatal(err) } views, err := root.Stop.Coordinator.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } payloads, err := f.journal.GenerationPayloads(t.Context()) if err != nil { t.Fatal(err) } if len(views) != 1 || !views[0].Installation.Empty || len(payloads) != 1 || len(payloads[0].Inventory.Entries) != 0 { t.Fatal("first registration did not persist an empty installation") } in := views[0].Installation v := generationReconciler{read: func(ctx context.Context) (generationInventory, error) { return generationRequiredInventory(ctx, f.claims, generationInventory{Census: rootparticipation.Census{Schema: 1, Box: in.Box, Enrollment: in.Enrollment, Migration: in.Migration, Revision: 1, LaunchRevision: 1, Complete: true}, Installations: views, Payloads: payloads}, func(in hostguard.RootInstallation) error { fresh, err := history.FreshInstallation(release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App}, rootHistoryInstallation(in)) if err != nil { return err } if !fresh { return os.ErrPermission } return nil }) }, kernel: func(context.Context) (string, string, error) { return "boot", generationInventoryDigest("kernel"), nil }} disk := &rollbackDisk{dir: t.TempDir()} source := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, reconcile: v.reconcile} if pins, err := source.StagedPins(t.Context(), root.Roots["opt"]); err != nil || len(pins) != 0 { t.Fatalf("registered creation refused: %v %v", pins, err) } if c, err := (&release.RootGenerations{Store: disk}).Completeness(); err != nil || !c.Complete { t.Fatalf("registered creation incomplete: %+v %v", c, err) } } func TestPlan14GenerationUnwokenSibling(t *testing.T) { for _, attack := range []string{"none", "no-proof", "consumed", "state-exists", "changed-during-scan"} { t.Run(attack, func(t *testing.T) { f, _, _ := generationLaunchFixture(t) views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } views[0].Installation.Empty = false views[0].Installation.Artifacts.StateDirectory = true in := views[0].Installation t.Setenv("CARLOS_DATA_DIR", t.TempDir()) snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: in.Box, Enrollment: in.Enrollment, Migration: in.Migration, Revision: 1, LaunchRevision: 1, Complete: true}, Installations: views, Payloads: generationPayloadFixture(views)} history := &release.RootHistory{Store: &rollbackDisk{dir: t.TempDir()}} binding := release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App} if err := history.RecordCreation(binding, rootHistoryInstallation(in)); err != nil { t.Fatal(err) } if attack == "consumed" { if err := history.ConsumeCreation(binding); err != nil { t.Fatal(err) } } if attack == "state-exists" { if err := os.MkdirAll(provision.InstanceDir(dataDir(), in.Host), 0700); err != nil { t.Fatal(err) } } fresh := func(in hostguard.RootInstallation) error { ok, err := history.FreshInstallation(binding, rootHistoryInstallation(in)) if err != nil { return err } if !ok { return os.ErrPermission } return nil } if attack == "no-proof" { fresh = nil } v := generationReconciler{read: func(ctx context.Context) (generationInventory, error) { return generationLocalObligations(ctx, snapshot, fresh) }, kernel: func(context.Context) (string, string, error) { if attack == "changed-during-scan" { if err := history.ConsumeCreation(binding); err != nil { t.Fatal(err) } } return "boot", generationInventoryDigest("kernel"), nil }} _, err = v.reconcile(t.Context(), nil) if (err == nil) != (attack == "none") { t.Fatalf("unwoken installation: %v", err) } }) } } func TestPlan14GenerationFreshPrelaunchHandover(t *testing.T) { if ok, err := generationUnlaunched(hostguard.FenceView{Installation: hostguard.RootInstallation{Empty: true}}, generationInventory{}, func(hostguard.RootInstallation) error { t.Fatal("empty installation requested new proof") return os.ErrPermission }); err != nil || ok { t.Fatal(ok, err) } view := hostguard.FenceView{Host: "h.example", Installation: hostguard.RootInstallation{Host: "h.example", Incarnation: "inc", Stamp: 2}, Retired: []hostguard.Captured{{Incarnation: "inc", Stamp: 1}}} fresh := func(in hostguard.RootInstallation) error { if in.Host == "h.example" && in.Incarnation == "inc" && in.Stamp == 1 { return nil } return os.ErrPermission } if ok, err := generationUnlaunched(view, generationInventory{}, fresh); err != nil || !ok { t.Fatal(ok, err) } view.Retired[0].Incarnation = "other" if ok, err := generationUnlaunched(view, generationInventory{}, fresh); err == nil || ok { t.Fatal(ok, err) } view.Retired[0].Incarnation = "inc" inv := generationInventory{Launches: []hostbox.LaunchInventory{{Target: hostguard.FenceTarget{Host: view.Host}}}} if ok, err := generationUnlaunched(view, inv, fresh); err != nil || ok { t.Fatal("launched installation treated as fresh", ok, err) } if ok, err := generationUnlaunched(view, generationInventory{}, func(hostguard.RootInstallation) error { return os.ErrInvalid }); err == nil || ok { t.Fatal("unreadable creation accepted", ok, err) } }