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 "errors"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/activator"
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostguard"
16 "github.com/carlosframework/platform/internal/registry"
17 "github.com/carlosframework/platform/internal/release"
18)
19
20const adminTokenHost = "shop.bab.oncarlos.com"
21
22type adminTokenFixture struct {
23 t *testing.T
24 claims release.Store
25 data string
26 backend *activator.ExecBackend
27 tokens claimAdminTokens
28 rt registry.Route
29 actor hostbox.Actor
30 verify error
31}
32
33// newAdminTokenFixture is a first unscheduled wake on box-1: a serving claim
34// whose executor is execBox, no local hostguard record yet (Activate runs at
35// Admit, after selection), a staged live release and no <host>.env at all.
36func newAdminTokenFixture(t *testing.T, execBox string) *adminTokenFixture {
37 t.Helper()
38 ctx := t.Context()
39 st := release.NewDirStore(t.TempDir())
40 now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
41 in := hostclaim.NewHolder{Family: "instance", Key: "apps/bab/jam/instances/" + adminTokenHost + ".json", Account: "bab", App: "jam", Host: adminTokenHost, Region: "ie", RecordRegion: "ie", CreatedAt: now}
42 body := []byte(`{"schema":1,"host":"shop.bab.oncarlos.com","account":"bab","app":"jam","region":"ie","created_at":"2026-10-06T12:00:00Z"}`)
43 absent, err := hostclaim.Read(ctx, st, adminTokenHost)
44 if err != nil {
45 t.Fatal(err)
46 }
47 o, err := hostclaim.Update(ctx, st, absent, hostclaim.BeginCreate(in, body, "create", "console", now))
48 if err != nil {
49 t.Fatal(err)
50 }
51 if _, err = hostclaim.Update(ctx, st, o, hostclaim.CompleteCreate("create", execBox)); err != nil {
52 t.Fatal(err)
53 }
54 root := t.TempDir()
55 data, opt := filepath.Join(root, "data"), filepath.Join(root, "opt")
56 for _, dir := range []string{filepath.Join(data, "hostguard"), filepath.Join(opt, "versions", "v1", "bab"), filepath.Join(opt, "live")} {
57 if err = os.MkdirAll(dir, 0o770); err != nil {
58 t.Fatal(err)
59 }
60 }
61 bin := filepath.Join(opt, "versions", "v1", "bab", "jam")
62 if err = os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o755); err != nil {
63 t.Fatal(err)
64 }
65 if err = os.Symlink(bin, filepath.Join(opt, "live", adminTokenHost)); err != nil {
66 t.Fatal(err)
67 }
68 reg, err := registry.Open(filepath.Join(root, "registry.db"))
69 if err != nil {
70 t.Fatal(err)
71 }
72 t.Cleanup(func() { reg.Close() })
73 f := &adminTokenFixture{t: t, claims: st, data: data, backend: &activator.ExecBackend{DataDir: data, OptDir: opt}, actor: hostbox.Actor{Box: "box-1", Region: "ie", Enrollment: "enr-1"}}
74 f.rt = registry.Route{Host: adminTokenHost, Socket: filepath.Join(root, "run", "app.sock"), DBPath: filepath.Join(data, adminTokenHost+".db")}
75 local := &productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: "agent", DataDir: data}, slots: map[string]*claimParticipation{}}
76 f.tokens = claimAdminTokens{
77 Guard: hostguard.Guard{DataDir: data, LockDir: t.TempDir(), Claims: hostguard.StoreReader{Store: st}},
78 Claims: st,
79 Verify: f.verifyActor,
80 Local: func(ctx context.Context, c *hostclaim.Claim, s hostbox.Selection) error {
81 a, err := f.verifyActor(ctx)
82 if err != nil {
83 return err
84 }
85 if s.Box != a.Box || s.Region != a.Region {
86 return hostbox.ErrChanged
87 }
88 return local.Check(ctx, a, c, s)
89 },
90 Policy: hostbox.Policy{Held: hostbox.RegistryHoldPolicy(reg)}.Check,
91 Now: time.Now,
92 }
93 return f
94}
95
96func (f *adminTokenFixture) verifyActor(context.Context) (hostbox.Actor, error) {
97 return f.actor, f.verify
98}
99
100func (f *adminTokenFixture) envPath() string { return filepath.Join(f.data, adminTokenHost+".env") }
101
102func (f *adminTokenFixture) token() string {
103 f.t.Helper()
104 raw, err := os.ReadFile(f.envPath())
105 if err != nil {
106 f.t.Fatal(err)
107 }
108 var tok string
109 for _, line := range strings.Split(string(raw), "\n") {
110 if v, ok := strings.CutPrefix(line, "CARLOS_ADMIN_TOKEN="); ok {
111 if tok != "" {
112 f.t.Fatalf("env file holds two tokens: %q", raw)
113 }
114 tok = v
115 }
116 }
117 return tok
118}
119
120func envHasToken(env []string, tok string) bool {
121 for _, kv := range env {
122 if kv == "CARLOS_ADMIN_TOKEN="+tok {
123 return true
124 }
125 }
126 return false
127}
128
129// B3 regression: the claim path bypasses the legacy launch preparation that
130// mints CARLOS_ADMIN_TOKEN. The production serving launch source must
131// initialize the token under the claim's own serving authority before the
132// read-only snapshot, and every later selection must see the same token.
133func TestClaimProductionFirstWakeInitializesAdminToken(t *testing.T) {
134 f := newAdminTokenFixture(t, "box-1")
135 launch := claimServingLaunchSource(f.backend, f.tokens)
136 first, err := launch(t.Context(), f.rt)
137 if err != nil {
138 t.Fatalf("first unscheduled wake: launch selection failed: %v", err)
139 }
140 tok := f.token()
141 if len(tok) != 64 {
142 t.Fatalf("minted token %q, want 64 hex characters", tok)
143 }
144 if !envHasToken(first.Env, tok) || first.Identity.Config == "" || first.Identity.Release == "" {
145 t.Fatalf("snapshot does not carry the minted token: %+v", first)
146 }
147 // Retry selection, still before Admit: no rotation, identical identity.
148 again, err := launch(t.Context(), f.rt)
149 if err != nil {
150 t.Fatal(err)
151 }
152 if f.token() != tok || again.Identity != first.Identity {
153 t.Fatalf("retry rotated the token or changed the launch identity: %q→%q %+v→%+v", tok, f.token(), first.Identity, again.Identity)
154 }
155 // After Admit activates the local record, the token is still stable.
156 if err = f.tokens.Guard.Activate(t.Context(), adminTokenHost, "box-1", nil); err != nil {
157 t.Fatal(err)
158 }
159 after, err := launch(t.Context(), f.rt)
160 if err != nil {
161 t.Fatal(err)
162 }
163 if f.token() != tok || after.Identity != first.Identity {
164 t.Fatalf("post-admission selection rotated the token: %q→%q", tok, f.token())
165 }
166}
167
168// With a local record in place and the token removed, the token is minted
169// again only through the guarded installation path (Guard.With).
170func TestClaimProductionAdminTokenUnderInstalledRecord(t *testing.T) {
171 f := newAdminTokenFixture(t, "box-1")
172 if err := f.tokens.Guard.Activate(t.Context(), adminTokenHost, "box-1", nil); err != nil {
173 t.Fatal(err)
174 }
175 if _, err := claimServingLaunchSource(f.backend, f.tokens)(t.Context(), f.rt); err != nil {
176 t.Fatalf("installed serving holder: %v", err)
177 }
178 if len(f.token()) != 64 {
179 t.Fatal("no token minted under the installed record")
180 }
181}
182
183// Missing or stale authority refuses the wake before any file is written.
184func TestClaimProductionAdminTokenRefusesWithoutAuthority(t *testing.T) {
185 t.Parallel()
186 for _, tc := range []struct {
187 name string
188 setup func(*adminTokenFixture)
189 }{
190 {"other executor", func(f *adminTokenFixture) {}},
191 {"identity unverifiable", func(f *adminTokenFixture) { f.verify = errors.New("enrollment revoked") }},
192 {"no authorizer", func(f *adminTokenFixture) { f.tokens = claimAdminTokens{} }},
193 {"stale local record", func(f *adminTokenFixture) {
194 // A record from an earlier incarnation is not this holder's installation.
195 if err := os.WriteFile(filepath.Join(f.data, "hostguard", adminTokenHost+".json"), []byte(`{"v":1,"host":"`+adminTokenHost+`","incarnation":"old","active_stamp":1,"retired_stamps":[]}`), 0o600); err != nil {
196 f.t.Fatal(err)
197 }
198 }},
199 } {
200 t.Run(tc.name, func(t *testing.T) {
201 box := "box-1"
202 if tc.name == "other executor" {
203 box = "box-2"
204 }
205 f := newAdminTokenFixture(t, box)
206 tc.setup(f)
207 if _, err := claimServingLaunchSource(f.backend, f.tokens)(t.Context(), f.rt); err == nil {
208 t.Fatal("launch selection succeeded without serving authority")
209 }
210 if _, err := os.Stat(f.envPath()); !errors.Is(err, os.ErrNotExist) {
211 t.Fatalf("token initialization wrote %s without authority: %v", f.envPath(), err)
212 }
213 })
214 }
215}
216
217// The snapshot itself stays read-only: it refuses a missing token and never
218// creates one, so the InputPolicy re-check under hostguard cannot mint.
219func TestSnapshotClaimLaunchNeverMintsAdminToken(t *testing.T) {
220 f := newAdminTokenFixture(t, "box-1")
221 if _, err := f.backend.SnapshotClaimLaunch(t.Context(), f.rt); err == nil {
222 t.Fatal("snapshot accepted a missing token")
223 }
224 if _, err := os.Stat(f.envPath()); !errors.Is(err, os.ErrNotExist) {
225 t.Fatalf("snapshot wrote %s: %v", f.envPath(), err)
226 }
227}
228