//go:build linux && rootrunner package host import ( "crypto/ed25519" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "net" "net/http" "os" "path/filepath" "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/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rollout" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) func TestPlan14DeletedRecordRetirementRootRunner(t *testing.T) { t.Setenv("PLAN14_R11_DELETION", "1") TestPlan14GenerationParkedRootRunner(t) } func TestPlan14ServingHandoverPinsRootRunner(t *testing.T) { if err := os.Mkdir("/var/lib/carlos-claims", 0700); err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll("/var/lib/carlos-claims") }) t.Setenv("PLAN14_R11_HANDOVER_ROOT", "1") TestPlan14ServingHandoverPins(t) } func r11DeletedRecordPins(t *testing.T, snapshot generationInventory, store release.Store) { t.Helper() ctx := t.Context() open := func(path string) rootstate.Store { s, err := rootstate.Open(path, true, false) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) return s } write := func(s rootstate.Store, key string, v any) { raw, err := json.Marshal(v) if err == nil { err = s.Write(key, raw) } if err != nil { t.Fatal(err) } } write(open(rootparticipation.Namespace), "census.json", snapshot.Census) fences := open("/var/lib/carlos-claims/authority/fences") in := snapshot.Installations[0].Installation write(fences, in.Host+".json", map[string]any{"Schema": 1, "Installation": in, "Retired": []hostguard.Captured{{Incarnation: in.Incarnation, Stamp: in.Stamp}}, "State": map[string]any{"v": 1, "host": in.Host, "incarnation": in.Incarnation, "active_stamp": 0, "retired_stamps": []uint64{in.Stamp}, "fence_journal": snapshot.Installations[0].Receipts}}) journal, err := hostbox.OpenLaunchJournal("/var/lib/carlos-claims/journals.db") if err != nil { t.Fatal(err) } service, err := hostbox.NewJournalService(journal) if err != nil { t.Fatal(err) } if err = service.Inventory.WriteStopInventory(ctx, snapshot.Payloads[0].Inventory); err != nil { t.Fatal(err) } target := snapshot.Launches[0].Target if err = journal.Prelaunch(ctx, hostbox.LaunchInventory{Target: target, ControllerPID: 77, ProcessStart: "closed-fixture", NoState: true, Groups: []hostbox.LaunchGroup{{RelativePath: "fixture", Device: 1, Inode: 1}}}); err != nil { t.Fatal(err) } if err = journal.CloseWriters(ctx, target); err != nil { t.Fatal(err) } if err = journal.Close(); err != nil { t.Fatal(err) } open(release.RootGenerationsDir) snapshotRead, err := generationInventoryAt(ctx, rootparticipation.Namespace, "/var/lib/carlos-claims/authority/fences", "/var/lib/carlos-claims/journals.db") if err != nil { t.Fatal("read production inventory", err) } snapshotRead.Retirements, err = productionGenerationRetirements(ctx) if err != nil { t.Fatal("read retirements", err) } if _, err = generationRequiredInventory(ctx, store, snapshotRead, productionGenerationCreation); err != nil { t.Fatal("retained obligations", err) } if _, err = productionGenerationRegistryInventory(ctx, snapshotRead); err != nil { t.Fatal("registry obligations", err) } if _, err = generationPhysicalPayloads(ctx, snapshotRead); err != nil { t.Fatal("physical obligations", err) } if _, _, err = productionGenerationKernelCensus(ctx); err != nil { t.Fatal("kernel census", err) } source := productionRootGenerationPins() lock := open(rootReleasePrivate) if err = rootstate.WithRelease(ctx, lock, func() error { pins, err := source.StagedPins(ctx, release.OptDir()) if err == nil && len(pins) != 0 { t.Fatal("unexpected pins", pins) } return err }); err != nil { t.Fatal("production pins after record deletion", err) } pub, priv, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } body := []byte("signed sibling") m := release.Manifest{App: "sibling", Version: "v9", Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}} if err = release.Ship(ctx, store, "bab", m, map[string][]byte{release.AdoptPlatform(): body}); err != nil { t.Fatal(err) } if err = release.Promote(ctx, store, priv, "bab", "sibling", "stable", "v9"); err != nil { t.Fatal(err) } if err = release.StageArtifact(release.OptDir(), "bab", "v9", "sibling", body); err != nil { t.Fatal(err) } manifest := release.StagedManifestPath(release.OptDir(), "bab", "v9", "sibling") if err = os.WriteFile(manifest, []byte("broken sibling manifest"), 0644); err != nil { t.Fatal(err) } if err = rootstate.WithRelease(ctx, lock, func() error { proof := release.NewRootProofGate(filepath.Join(t.TempDir(), "policy.json"), filepath.Join(t.TempDir(), "bakes")) _, err := release.StageHeldVerifiedWithPins(ctx, store, pub, nil, release.OptDir(), registry.Route{Host: "sibling.example", Account: "bab", App: "sibling", Kind: "instance", Channel: "stable", Status: "landing", HoldOp: "repair"}, "v9", proof, source) return err }); err != nil { t.Fatal("sibling repair after deleted host", err) } raw, err := os.ReadFile(manifest) if err != nil || string(raw) == "broken sibling manifest" { t.Fatal("sibling manifest not repaired", err) } if _, _, err = store.Get(ctx, provision.RecordKey(in.Account, in.App, in.Host)); !errors.Is(err, os.ErrNotExist) { t.Fatal("record resurrected", err) } } func TestPlan14SettingsSuccessorProductionRootRunner(t *testing.T) { for _, path := range []string{"/var/lib/carlos-claims", "/var/lib/carlos-release", rootRetryProjection, "/run/carlos"} { if err := os.Mkdir(path, 0755); err != nil { t.Fatal("existing fixture", path, err) } t.Cleanup(func() { os.RemoveAll(path) }) } open := func(path string) rootstate.Store { s, err := rootstate.Open(path, true, false) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) return s } c := rootparticipation.Census{Schema: 1, Box: "box-1", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true} raw, _ := json.Marshal(c) if err := open(rootparticipation.Namespace).Write("census.json", raw); err != nil { t.Fatal(err) } failures := open(rollout.RootFailuresDir) if _, err := rollout.InitializeRootFailures(failures, rootUpdatesMigration(c), nil); err != nil { t.Fatal(err) } testInventoryRegistrationWith(t, true, nil, open("/var/lib/carlos-claims/authority/fences"), func(t *testing.T, f *recoveryFixture, root ClaimRootHTTP, row registry.Route, actor hostbox.Actor) { t.Setenv("CARLOS_DATA_DIR", root.Roots["data"]) t.Setenv("CARLOS_OPT_DIR", root.Roots["opt"]) pub, priv, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } m := release.Manifest{App: row.App, Version: "v1", Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}} body := []byte("signed settings witness") if err = release.Ship(t.Context(), f.claims, row.Account, m, map[string][]byte{release.AdoptPlatform(): body}); err != nil { t.Fatal(err) } if err = release.Promote(t.Context(), f.claims, priv, row.Account, row.App, "stable", "v1"); err != nil { t.Fatal(err) } if err = os.MkdirAll(filepath.Dir(row.Socket), 0755); err != nil { t.Fatal(err) } listener, err := net.Listen("unix", row.Socket) if err != nil { t.Fatal(err) } server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ready")) })} go server.Serve(listener) defer server.Close() source := productionRootGenerations(f.claims, root.Roots["opt"]) source.pub = func() (ed25519.PublicKey, error) { return pub, nil } digest := sha256.Sum256(body) g := root.Stop.Coordinator.Guard capture, err := g.Capture(row.Host) if err != nil { t.Fatal(err) } prior, err := g.Root.Installation(t.Context(), row.Host, capture) if err != nil { t.Fatal(err) } for wake := 0; wake < 2; wake++ { opened, err := productionRootUpdates(dataDir())(t.Context(), f.claims, pub) if err != nil { t.Fatal(err) } resolved, err := opened.Resolve(t.Context(), row.Host) if err != nil { t.Fatal("settings wake", wake, err) } if wake == 1 && (resolved.Build.State != nil || resolved.Consumed != nil) { t.Fatal("successor reused foreign completion") } if wake == 0 { if err = resolved.Save(release.Consumed{Resolved: &release.ResolvedRequest{ID: 11, Target: "v1"}}); err != nil { t.Fatal(err) } } opened.Close() current, err := g.Capture(row.Host) if err != nil { t.Fatal(err) } installed, err := g.Root.Installation(t.Context(), row.Host, current) if err != nil { t.Fatal(err) } b := release.GenerationBinding{Binding: release.HistoryBinding{Host: row.Host, Account: row.Account, App: row.App}, Target: release.GenerationOwner{Host: installed.Host, Incarnation: installed.Incarnation, Stamp: installed.Stamp, Box: installed.Box, Enrollment: installed.Enrollment}, Installation: rootHistoryInstallation(installed), Record: installed.Record, Version: "v1", Digest: hex.EncodeToString(digest[:]), Attempt: fmt.Sprintf("wake-%d", wake)} finish, close, err := source.beginUpdate(t.Context(), source, b) if err != nil { t.Fatal("production begin", wake, err) } if err = finish(fmt.Sprintf("invocation-%d", wake), nil, func() error { return nil }); err != nil { close() t.Fatal(err) } close() if wake == 0 { observed := f.read() admitted, err := hostclaim.Update(t.Context(), f.claims, observed, hostclaim.AdmitWriter(hostclaim.Admission{ObservedSession: observed.Claim.Holder.Session, ObservedGeneration: observed.Claim.Holder.Generation, Box: actor.Box, Region: actor.Region, Mode: "cold", NewGeneration: "second-generation"})) if err != nil { t.Fatal(err) } next := prior next.Stamp = admitted.Claim.Holder.Stamp if next.Stamp <= prior.Stamp { t.Fatal("cold admission did not advance installation") } intent, _ := json.Marshal(admitted) if err = g.RebindRootInventory(t.Context(), capture, prior.InventoryRevision, next, intent, func(claim *hostclaim.Claim) error { if !hostclaim.SameWriterIdentity(admitted, hostclaim.Observed{Host: row.Host, Claim: claim}) { return os.ErrPermission } return nil }, func() error { return nil }); err != nil { t.Fatal(err) } } } var saved rootUpdateRecord raw, err := failures.Read(rootSettingsKey(row.Host, rootHistoryInstallation(prior))) if err != nil || json.Unmarshal(raw, &saved) != nil || saved.Build.State == nil || saved.Build.State.Completed != 11 { t.Fatal("predecessor settings not retained", err) } }) }