//go:build unix package hostguard import ( "context" "encoding/json" "errors" "github.com/carlosframework/platform/internal/hostclaim" "testing" ) // Fix round 1, finding 1 (and round 2): an obsolete pending intent is settled into a // quarantine the migration census may inspect, while every execution read // still refuses it. A landed commit is recorded as the commit would have // recorded it; an unlanded one leaves the record's installation. func TestSettleObsoleteRootInventory(t *testing.T) { for _, landed := range []bool{true, false} { name := "unlanded" if landed { name = "landed" } t.Run(name, func(t *testing.T) { ctx := t.Context() f, target, captured := fenceFixture(t) f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "console" f.g.Root = NewRootAuthority(&rootTestStore{dir: t.TempDir()}) in := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "console", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1, InventoryRevision: 1} if err := f.g.EstablishRoot(ctx, in, allow); err != nil { t.Fatal(err) } before := in in.InventoryRevision++ fault := errors.New("interrupted") if err := f.g.CommitRootInventory(ctx, in, []byte("exact"), allow, nil, func(RootInstallation) error { return fault }); !errors.Is(err, fault) { t.Fatal(err) } if err := f.g.SettleObsoleteRootInventory(ctx, target.Host, []byte("forged"), func(context.Context, RootInstallation) (bool, error) { return landed, nil }); !errors.Is(err, ErrInvalidInput) { t.Fatal("forged intent settled:", err) } var got RootInstallation if err := f.g.SettleObsoleteRootInventory(ctx, target.Host, []byte("exact"), func(_ context.Context, p RootInstallation) (bool, error) { got = p; return landed, nil }); err != nil { t.Fatal(err) } if got != in { t.Fatalf("applied asked about %+v, want the pending %+v", got, in) } r, err := f.g.Root.read(target.Host) if err != nil || !r.quarantined || !r.settled || r.pendingInstallation != nil { t.Fatalf("after settlement: %+v %v", r, err) } want := before if landed { want = in } if r.installation != want { t.Fatalf("installation %+v, want %+v", r.installation, want) } // Execution reads refuse; the census lists it, marked Settled. if _, err := f.g.Root.View(ctx, target.Host, captured); !errors.Is(err, ErrFenced) { t.Fatal("execution view of a settled installation:", err) } if err := r.admits(f.obs.Claim, captured); !errors.Is(err, ErrFenced) { t.Fatal("admission of a settled installation:", err) } views, err := f.g.Root.MigrationInstallations(ctx) if err != nil || len(views) != 1 || !views[0].Settled || views[0].Installation != want { t.Fatalf("census: %+v %v", views, err) } // Settled without the quarantine is malformed. raw, err := f.g.Root.store.Read(target.Host + ".json") if err != nil { t.Fatal(err) } var saved map[string]any if err := json.Unmarshal(raw, &saved); err != nil { t.Fatal(err) } delete(saved, "Quarantined") forged, _ := json.Marshal(saved) if err := f.g.Root.store.Write(target.Host+".json", forged); err != nil { t.Fatal(err) } if _, err := f.g.Root.read(target.Host); !errors.Is(err, ErrMalformedRecord) { t.Fatal("settled without quarantine:", err) } }) } // The superseded-claim abandonment ends in the same terminal state (fix // round 2): it keeps its exact-intent and fresh-claim checks, reconciles a // landed commit, and leaves an installation the census can inspect and // execution still refuses. for _, landed := range []bool{true, false} { name := "abandoned-unlanded" if landed { name = "abandoned-landed" } t.Run(name, func(t *testing.T) { ctx := t.Context() f, target, captured := fenceFixture(t) f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "console" f.g.Root = NewRootAuthority(&rootTestStore{dir: t.TempDir()}) in := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "console", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1, InventoryRevision: 1} if err := f.g.EstablishRoot(ctx, in, allow); err != nil { t.Fatal(err) } before := in in.InventoryRevision++ fault := errors.New("interrupted") if err := f.g.CommitRootInventory(ctx, in, []byte("exact"), allow, nil, func(RootInstallation) error { return fault }); !errors.Is(err, fault) { t.Fatal(err) } answer := func(context.Context, RootInstallation) (bool, error) { return landed, nil } if err := f.g.AbandonRootInventory(ctx, target.Host, []byte("forged"), func(*hostclaim.Claim) bool { return true }, answer); !errors.Is(err, ErrInvalidInput) { t.Fatal("forged intent abandoned:", err) } if err := f.g.AbandonRootInventory(ctx, target.Host, []byte("exact"), func(*hostclaim.Claim) bool { return false }, answer); !errors.Is(err, ErrFenced) { t.Fatal("unsuperseded intent abandoned:", err) } if pending, err := f.g.Root.PendingInstallations(); err != nil || len(pending) != 1 { t.Fatalf("refused abandonment changed the intent: %d %v", len(pending), err) } if err := f.g.AbandonRootInventory(ctx, target.Host, []byte("exact"), func(*hostclaim.Claim) bool { return true }, answer); err != nil { t.Fatal(err) } r, err := f.g.Root.read(target.Host) if err != nil || !r.quarantined || !r.settled || r.pendingInstallation != nil { t.Fatalf("after abandonment: %+v %v", r, err) } want := before if landed { want = in } if r.installation != want { t.Fatalf("installation %+v, want %+v", r.installation, want) } if _, err := f.g.Root.View(ctx, target.Host, captured); !errors.Is(err, ErrFenced) { t.Fatal("execution view of an abandoned installation:", err) } if err := r.admits(f.obs.Claim, captured); !errors.Is(err, ErrFenced) { t.Fatal("admission of an abandoned installation:", err) } views, err := f.g.Root.MigrationInstallations(ctx) if err != nil || len(views) != 1 || !views[0].Settled || views[0].Installation != want { t.Fatalf("census of an abandoned installation: %+v %v", views, err) } }) } } // unlanded is an applied answer for an intent whose commit never landed. func unlanded(context.Context, RootInstallation) (bool, error) { return false, nil }