package host import ( "context" "crypto/ed25519" "net" "net/http" "strings" "testing" "time" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostops" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/release" ) func TestPlan9CleanupEnrollmentDoesNotGrantExecution(t *testing.T) { t.Parallel() root := ed25519.NewKeyFromSeed(make([]byte, 32)) evidence := ed25519.NewKeyFromSeed([]byte(strings.Repeat("e", 32))) actor := hostbox.Actor{Box: "box", Enrollment: strings.Repeat("a", 32), Revision: 1, Binding: "1", Region: "ie"} identity := handoverRecoveryIdentity(t, actor) cloudActor, err := identity.Roster.Actor(t.Context(), actor.Box) if err != nil { t.Fatal(err) } store := release.NewDirStore(t.TempDir()) row := hostactors.Box{BoxID: actor.Box, Enrollment: actor.Enrollment, Namespace: identity.Namespace, Label: actor.Box, Region: actor.Region, Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: cloudActor.Cloud.Account, Region: cloudActor.Cloud.Region, InstanceID: cloudActor.Cloud.InstanceID}, EvidenceSHA256: strings.Repeat("c", 64), CensusPublicKey: evidence.Public().(ed25519.PublicKey), State: "quarantined", QuarantineRevision: 1, LossIDs: []string{"loss"}} roster := hostactors.Roster{V: 1, DeploymentBucket: "deployment", Revision: 1, RequestLimitNS: int64(hostbox.RequestLimit), Boxes: map[string]hostactors.Box{actor.Box: row}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}} write := func() { raw, err := release.SignRootEnvelope(release.RootSigner(root), roster) if err != nil { t.Fatal(err) } _, etag, _ := store.Get(t.Context(), hostactors.Key) if _, err = store.Put(t.Context(), hostactors.Key, raw, etag); err != nil { t.Fatal(err) } } write() directory := hostactors.Directory{Store: store, PublicKey: root.Public().(ed25519.PublicKey), Bucket: "deployment"} identity.Roster = productionClaimRoster{Directory: directory, DataBucket: identity.Bucket, FloorDir: t.TempDir()} if _, err = identity.Verify(t.Context()); err == nil { t.Fatal("quarantine granted work") } cleanup, err := verifyClaimCleanupEnrollment(t.Context(), identity, directory) if err != nil { t.Fatal(err) } if cleanup.actor.Eligible || !cleanup.actor.Quarantined { t.Fatal("cleanup projection can grant execution") } authority := &hostops.ProductionAuthority{Store: store, Directory: directory, Now: time.Now} owners := ClaimOwnerBindings{Identity: identity, cleanupEnrollment: cleanup, RetirementAuthority: authority, EvidenceKey: evidence} if _, err = owners.actorForConstruction(t.Context(), "agent"); err == nil { t.Fatal("agent used cleanup projection") } if _, err = owners.actorForConstruction(t.Context(), "adopt"); err == nil { t.Fatal("adopt used cleanup projection") } if _, err = owners.actorForConstruction(t.Context(), "instance-control"); err != nil { t.Fatal(err) } stop, err := owners.claimEvidenceCoordinator(t.Context(), store, "instance-control", cleanup.actor) if err != nil || len(stop.EvidenceSigner) == 0 { t.Fatalf("cleanup signer: %v", err) } owners.EvidenceKey = root if _, err = owners.claimEvidenceCoordinator(t.Context(), store, "instance-control", cleanup.actor); err == nil { t.Fatal("unenrolled key accepted") } row.Enrollment = strings.Repeat("b", 32) roster.Boxes[actor.Box] = row write() if err = cleanup.configureSigner(t.Context(), stop, evidence); err == nil { t.Fatal("old enrollment signed after replacement") } } func TestPlan9RootWithdrawalRefusesNewHandover(t *testing.T) { f := newRecoveryFixture(t) controller := f.controller("boot-1", "controller-1", 4101, "start-1") _ = controller root := ClaimRootHTTP{RequireNewWork: func(context.Context) error { return hostwrite.ErrUnavailable }, Stop: ClaimStopHTTP{Coordinator: f.root(recoveryStopDrain{}), Authenticate: func(context.Context, net.Conn) (StopPeer, error) { return f.peer, nil }}} socket := startRetirementRecoveryTransport(t, root) if got := postRecoveryHandover(t, socket, coldHandoverRequest{}); got != http.StatusForbidden { t.Fatalf("withdrawn handover=%d", got) } }