carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/hostactors"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostevidence"
16 "github.com/carlosframework/platform/internal/hostguard"
17 "github.com/carlosframework/platform/internal/release"
18)
19
20func TestPlan11ReleaseNeedsExactSignedRootStop(t *testing.T) {
21 t.Parallel()
22 for _, bad := range []string{"", "unsigned", "wrong-stamp", "retirement", "wrong-enrollment", "wrong-session", "session-zero", "zero-stale", "zero-as-stopped", "positive-as-no-process"} {
23 t.Run(bad, func(t *testing.T) {
24 ctx := context.Background()
25 s := release.NewDirStore(t.TempDir())
26 root := ed25519.NewKeyFromSeed(make([]byte, 32))
27 boxKey := ed25519.NewKeyFromSeed([]byte(strings.Repeat("b", 32)))
28 d := hostactors.Directory{Store: s, PublicKey: root.Public().(ed25519.PublicKey), Bucket: "bucket"}
29 en := strings.Repeat("a", 32)
30 roster := hostactors.Roster{V: 1, Revision: 1, DeploymentBucket: "bucket", Boxes: map[string]hostactors.Box{"box": {Enrollment: en, BoxID: "box", Label: "box", Namespace: "platform", Region: "ie", Pool: "default", State: "active", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-box"}, EvidenceSHA256: strings.Repeat("d", 64), CensusPublicKey: boxKey.Public().(ed25519.PublicKey)}}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}}
31 wire, _ := release.SignRootEnvelope(release.RootSigner(root), roster)
32 if _, err := s.Put(ctx, hostactors.Key, wire, ""); err != nil {
33 t.Fatal(err)
34 }
35 host := "shop.example"
36 created := time.Date(2026, 10, 9, 0, 0, 0, 0, time.UTC)
37 inc, _ := hostclaim.Incarnation("bab", "jam", host, created)
38 claim := hostclaim.Claim{V: 2, Gen: 1, Host: host, Holder: &hostclaim.Holder{Family: "instance", Key: "apps/bab/jam/instances/" + host + ".json", Account: "bab", App: "jam", Incarnation: inc, CreatedAt: created, Location: "instances/" + host + "/" + inc, Stamp: 7, Session: 7, WriterBox: "box"}, Op: hostclaim.Op{ID: "reloc", Kind: hostclaim.KindRelocating, Phase: "releasing", Exec: "box", Intent: map[string]string{"source_box": "box", "source_enrollment": en, "region": "ie"}}, Obligations: []hostclaim.Obligation{{ID: "relocation-release", Kind: "relocation-release", Op: "reloc", Box: "box", Region: "ie", Owner: "region:ie", Retirement: "install-7"}}}
39 if bad == "session-zero" || strings.HasPrefix(bad, "zero-") {
40 claim.Holder.Session = 0
41 claim.Holder.WriterBox = ""
42 }
43 raw, _ := json.Marshal(claim)
44 key, _ := hostclaim.Key(host)
45 tag, err := s.Put(ctx, key, raw, "")
46 if err != nil {
47 t.Fatal(err)
48 }
49 obs := hostclaim.Observed{Host: host, Claim: &claim, ETag: tag}
50 if err := hostclaim.CompleteObligation("reloc", "relocation-release")(&claim); err == nil {
51 t.Fatal("generic chore completion discharged release")
52 }
53 receipt := StopReceipt{V: 1, ID: "stop-1", Kind: "stopped", Host: host, Incarnation: inc, InstallationStamp: 7, WriterSession: 7, OperationID: "reloc", Phase: "releasing", ObligationID: "relocation-release", Box: "box", Enrollment: en, Region: "ie", EvidenceAt: time.Now(), RequestLimitNS: int64(120 * time.Second), ClaimGen: 1, FenceID: "fence", InventorySHA256: strings.Repeat("e", 64), LocalJournalSHA256: strings.Repeat("f", 64), ProducerDrainDigest: strings.Repeat("a", 64), CgroupIdentities: []string{"root-role"}}
54 if claim.Holder.Session == 0 || bad == "positive-as-no-process" {
55 receipt.WriterSession = claim.Holder.Session
56 receipt.Kind = StopReceiptNoProcess
57 receipt.FenceID = ""
58 receipt.ProducerDrainDigest = ""
59 receipt.ClaimETag = tag
60 }
61 switch bad {
62 case "zero-stale":
63 receipt.ClaimETag = "old-etag"
64 case "zero-as-stopped":
65 receipt.Kind = "stopped"
66 case "wrong-stamp":
67 receipt.InstallationStamp++
68 case "retirement":
69 receipt.ObligationID = "park-local"
70 case "wrong-enrollment":
71 receipt.Enrollment = strings.Repeat("f", 32)
72 case "wrong-session":
73 receipt.WriterSession++
74 }
75 body, _ := json.Marshal(receipt)
76 receiptKey := stopEvidenceKey(host, "reloc", "box", "stop-1")
77 sum := sha256.Sum256(body)
78 if bad == "unsigned" {
79 _, err = s.Put(ctx, receiptKey, body, "")
80 } else {
81 err = hostevidence.Publish(ctx, s, boxKey, receiptKey, body)
82 }
83 if err != nil {
84 t.Fatal(err)
85 }
86 got, err := (RelocationReleaseReceipts{Store: s, Directory: d}).Complete(ctx, obs, receiptKey, hex.EncodeToString(sum[:]))
87 if bad != "" && bad != "session-zero" {
88 if err == nil {
89 t.Fatal("invalid release receipt accepted")
90 }
91 return
92 }
93 if err != nil {
94 t.Fatal(err)
95 }
96 if len(got.Claim.Obligations) != 0 || got.Claim.Holder.Stamp != 7 || got.Claim.Op.Phase != "releasing" {
97 t.Fatal("release completion changed authority")
98 }
99 replayed, err := (RelocationReleaseReceipts{Store: s, Directory: d}).Complete(ctx, got, receiptKey, hex.EncodeToString(sum[:]))
100 if err != nil || replayed.ETag != got.ETag {
101 t.Fatal("release replay changed claim", err)
102 }
103
104 })
105 }
106}
107
108func TestPlan11RootReleaseRequiresItsStopOnlyObligation(t *testing.T) {
109 c, req, _ := claimStopFixture(t)
110 o := c.Claims.(stopClaimReader).obs
111 o.Claim.Op.Kind = hostclaim.KindRelocating
112 o.Claim.Op.Phase = "releasing"
113 o.Claim.Op.Intent = map[string]string{"region": "ie", "source_box": "box-1"}
114 req.Phase = "releasing"
115 req.Region = "ie"
116 req.ObligationID = "relocation-release"
117 target := hostguard.FenceTarget{Host: req.Host, Incarnation: req.Incarnation, Stamp: req.InstallationStamp, Session: req.WriterSession, Op: req.OperationID, Phase: req.Phase, Box: req.Box, Region: req.Region, ObligationID: req.ObligationID}
118 if hostguard.StopAuthorityMatches(o.Claim, target) {
119 t.Fatal("release accepted without obligation")
120 }
121 o.Claim.Obligations = []hostclaim.Obligation{{ID: "relocation-release", Kind: "relocation-release", Op: req.OperationID, Region: "ie", Box: req.Box, Owner: "region:ie", Retirement: "install"}}
122 if !hostguard.StopAuthorityMatches(o.Claim, target) {
123 t.Fatal("bound release refused")
124 }
125 o.Claim.Obligations[0].Park = &hostclaim.ParkScope{}
126 if hostguard.StopAuthorityMatches(o.Claim, target) {
127 t.Fatal("release with park scope accepted")
128 }
129}
130