package hostops import ( "context" "crypto/ed25519" "crypto/rand" "encoding/json" "strings" "testing" "time" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostevidence" "github.com/carlosframework/platform/internal/release" ) func TestBothNewParksReuseAuthenticatedPrePurgeStopReceipt(t *testing.T) { ctx := context.Background() pub, key, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } boxPub, boxKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } store := release.NewDirStore(t.TempDir()) at := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC) enrollment := strings.Repeat("a", 32) roster := hostactors.Roster{V: 1, DeploymentBucket: "bucket", Revision: 3, RequestLimitNS: int64(hostbox.RequestLimit), Regions: map[string]hostactors.Region{"ie": {}}, Boxes: map[string]hostactors.Box{ "box": {CensusPublicKey: boxPub, Enrollment: enrollment, Namespace: "platform", BoxID: "box", Label: "box", Region: "ie", Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "ie", InstanceID: "i-box"}, EvidenceSHA256: strings.Repeat("c", 64), State: "active"}, }, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}} signed, err := release.SignRootEnvelope(release.RootSigner(key), roster) if err != nil { t.Fatal(err) } if _, err = store.Put(ctx, hostactors.Key, signed, ""); err != nil { t.Fatal(err) } inc := strings.Repeat("b", 64) obs := hostclaim.Observed{Host: "x.example", ETag: "claim-etag", Claim: &hostclaim.Claim{V: 2, Host: "x.example", Gen: 10, Holder: &hostclaim.Holder{Incarnation: inc, Stamp: 4, Session: 5, WriterBox: "box"}, Op: hostclaim.Op{Kind: hostclaim.KindParking, ID: "purge", Exec: "box"}}} receipt := StopReceipt{Region: "ie", ClaimETag: "original-claim-etag", ObservedBootID: "boot-after-recovery", V: 1, ID: "receipt", Host: obs.Host, Incarnation: inc, InstallationStamp: 4, WriterSession: 5, OperationID: "purge", Phase: "stopping", ObligationID: "pre-teardown-stop", Box: "box", Enrollment: enrollment, BootID: "boot", ControllerID: "controller", ClaimGen: 9, DirectoryRevision: "3", Kind: "stopped", EvidenceAt: at, FenceID: "fence", InventorySHA256: strings.Repeat("d", 64), LocalJournalSHA256: strings.Repeat("e", 64), ProducerDrainDigest: strings.Repeat("f", 64), CgroupIdentities: []string{"cgroup"}, RequestLimitNS: int64(hostbox.RequestLimit)} raw, err := json.Marshal(receipt) if err != nil { t.Fatal(err) } receiptKey := "control/hosts/x.example/evidence/purge/box/receipt.json" if err = hostevidence.Publish(ctx, store, boxKey, receiptKey, raw); err != nil { t.Fatal(err) } authority := ProductionAuthority{Store: store, Directory: hostactors.Directory{Store: store, PublicKey: pub, Bucket: "bucket"}, Now: func() time.Time { return at.Add(time.Hour) }} for _, consumer := range []struct{ id, target string }{{"new-instance-park", "instances/x.example/" + inc}, {"new-staging-park", "moves/x.example"}} { ref, err := authority.chooseEvidence(ctx, obs, consumer.id, "purge", "box") if err != nil { t.Fatalf("%s did not reuse original stop receipt: %v", consumer.id, err) } if ref.ObligationID != consumer.id || ref.SourceObligationID != "pre-teardown-stop" || ref.Key != receiptKey { t.Fatalf("lost source/consumer binding: %+v", ref) } scope, err := retirementScope(obs.Host, inc, consumer.target, map[string]hostclaim.RetirementEvidenceRef{"box": ref}) if err != nil || scope.Location != consumer.target || !scope.EvidenceAt.Equal(at) { t.Fatalf("scope = %+v, %v", scope, err) } for _, field := range []string{"region", "claim_etag", "observed_boot_id", "request_limit_ns"} { var changed map[string]json.RawMessage if err := json.Unmarshal(raw, &changed); err != nil { t.Fatal(err) } changed[field] = json.RawMessage(`"changed"`) if field == "request_limit_ns" { changed[field] = json.RawMessage(`1`) } tampered, err := json.Marshal(changed) if err != nil { t.Fatal(err) } _, etag, err := store.Get(ctx, receiptKey) if err != nil { t.Fatal(err) } etag, err = store.Put(ctx, receiptKey, tampered, etag) if err != nil { t.Fatal(err) } forged := ref forged.SHA256 = evidenceDigest(tampered) if _, err := authority.verifyEvidence(ctx, obs, consumer.id, "purge", forged, false); err == nil { t.Fatalf("accepted tampered %s with original signature", field) } if _, err := store.Put(ctx, receiptKey, raw, etag); err != nil { t.Fatal(err) } } ref.SourceObligationID = "different-source" if _, err = authority.verifyEvidence(ctx, obs, consumer.id, "purge", ref, false); err == nil { t.Fatal("accepted a substituted producer obligation") } } }