package hostguard import ( "context" "errors" "github.com/carlosframework/platform/internal/hostclaim" "testing" "time" ) func TestPlan14RootRebind(t *testing.T) { for _, failure := range []string{"none", "admission", "revision", "identity", "record", "box", "enrollment", "missing", "before commit", "crash"} { t.Run(failure, func(t *testing.T) { f, target, captured := fenceFixture(t) store := &rootTestStore{dir: t.TempDir()} f.g.Root = NewRootAuthority(store) f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "app" old := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "app", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1, InventoryRevision: 3} if err := f.g.EstablishRoot(t.Context(), old, allow); err != nil { t.Fatal(err) } if _, err := f.g.BeginFence(t.Context(), target); err != nil { t.Fatal(err) } at := time.Now().UTC() if err := f.g.CompleteFence(t.Context(), target, []byte("retained receipt"), at, func(context.Context, FenceTarget, []byte, time.Time) error { return nil }); err != nil { t.Fatal(err) } before, _ := store.Read(target.Host + ".json") next := old next.Stamp++ next.InventoryRevision = 1 f.obs.Claim.Holder.Stamp = next.Stamp f.obs.Claim.Holder.Session = next.Stamp revision := old.InventoryRevision if failure == "revision" { revision++ } if failure == "identity" { next.Account = "other" } if failure == "record" { next.Record = "other" } if failure == "box" { next.Box = "other" } if failure == "enrollment" { next.Enrollment = "other" } if failure == "missing" { captured.Incarnation = "missing" } calls, effects := 0, 0 crash := errors.New("crash") check := func(*hostclaim.Claim) error { calls++ if failure == "admission" || failure == "before commit" && calls == 2 { return ErrFenced } return nil } commit := func() error { if failure == "crash" { return crash } effects++ return nil } err := f.g.RebindRootInventory(t.Context(), captured, revision, next, []byte("intent"), check, commit) if failure == "crash" { if !errors.Is(err, crash) { t.Fatal(err) } if _, err := f.g.Root.View(t.Context(), target.Host, captured); !errors.Is(err, ErrFenced) { t.Fatal("pending allowed", err) } f.g.Root = NewRootAuthority(store) err = f.g.RebindRootInventory(t.Context(), captured, revision, next, []byte("intent"), allow, func() error { effects++; return nil }) } if failure != "none" && failure != "crash" { if err == nil || effects != 0 { t.Fatal("refusal bypass", err, effects) } if failure != "before commit" { after, _ := store.Read(target.Host + ".json") if string(after) != string(before) { t.Fatal("refusal changed root") } } return } if err != nil || effects != 1 { t.Fatal(err, effects) } if err = f.g.RebindRootInventory(context.Background(), captured, revision, next, []byte("intent"), allow, func() error { t.Fatal("replay effect"); return nil }); err != nil { t.Fatal(err) } view, err := f.g.Root.View(t.Context(), target.Host, Captured{Incarnation: next.Incarnation, Stamp: next.Stamp}) if err != nil || view.Installation != next || view.Pending == nil || *view.Pending != target || len(view.Retired) != 1 || view.Retired[0] != captured || len(view.Receipts) != 1 || view.Receipts[0].Target != target || string(view.Receipts[0].Bytes) != "retained receipt" || !view.Receipts[0].EvidenceAt.Equal(at) { t.Fatal("lost denials", view, err) } }) } }