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
11 "github.com/carlosframework/platform/internal/activator"
12 "github.com/carlosframework/platform/internal/appconfig"
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/instancekey"
17 "github.com/carlosframework/platform/internal/registry"
18 "github.com/carlosframework/platform/internal/release"
19)
20
21// Ruling 26 on the claim path, which has no control socket before O6: a
22// keyless replica is never selected (its helper would mint), a key that is
23// not the home's is refused, and the home's key is selected and named.
24func TestClaimReplicaLaunchIsGated(t *testing.T) {
25 t.Parallel()
26 homeKey := strings.Repeat("H", 43) + "="
27 for _, tc := range []struct {
28 name, config string
29 refused bool
30 }{
31 {"keyless", "", true},
32 {"another key", activator.InstanceKeyVar + "=configured-B\n", true},
33 {"the home's key", activator.InstanceKeyVar + "=" + homeKey + "\n", false},
34 } {
35 t.Run(tc.name, func(t *testing.T) {
36 f := newAdminTokenFixture(t, "box-1")
37 f.rt.Replica = true
38 f.backend.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(homeKey), true }
39 f.backend.ConfiguredKey = func(context.Context, registry.Route) (string, error) { return instancekey.Digest(homeKey), nil }
40 own := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("c", 64) + "\n"
41 if err := os.WriteFile(f.envPath(), []byte(own), 0o600); err != nil {
42 t.Fatal(err)
43 }
44 if tc.config != "" {
45 if err := os.WriteFile(filepath.Join(f.data, adminTokenHost+".config.env"), []byte(tc.config), 0o600); err != nil {
46 t.Fatal(err)
47 }
48 }
49 launch, err := claimServingLaunchSource(f.backend, f.tokens)(t.Context(), f.rt)
50 if tc.refused {
51 if !errors.Is(err, activator.ErrReplicaKey) {
52 t.Fatalf("launch source = %v, want ErrReplicaKey", err)
53 }
54 } else {
55 found := false
56 for _, kv := range launch.Env {
57 found = found || kv == activator.InstanceKeyVar+"="+homeKey
58 }
59 if err != nil || !found {
60 t.Fatalf("launch source = %v, key delivered %v", err, found)
61 }
62 }
63 if raw, _ := os.ReadFile(f.envPath()); string(raw) != own {
64 t.Fatal("a replica's claim launch changed its own env file: a replica never mints")
65 }
66 })
67 }
68}
69
70// Final review I4: the claim's input policy asks whether a Work's identity
71// still names this host's inputs, and launches nothing. It snapshots
72// without the replica gate, so a configured-key replica costs it no bucket
73// read, and its answer keeps the policy's contract (nil or ErrChanged),
74// never a launch refusal. The launch source on the same backend runs the
75// gate, and its fresh read, as before.
76func TestClaimInputPolicyNeverReadsTheBucket(t *testing.T) {
77 homeKey := strings.Repeat("H", 43) + "="
78 f := newAdminTokenFixture(t, "box-1")
79 f.rt.Replica = true
80 f.backend.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(homeKey), true }
81 reads := 0
82 f.backend.ConfiguredKey = func(context.Context, registry.Route) (string, error) {
83 reads++
84 return "", errors.New("bucket unreachable") // a gate that ran here would refuse
85 }
86 if err := os.WriteFile(f.envPath(), []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("c", 64)+"\n"), 0o600); err != nil {
87 t.Fatal(err)
88 }
89 if err := os.WriteFile(filepath.Join(f.data, adminTokenHost+".config.env"), []byte(activator.InstanceKeyVar+"="+homeKey+"\n"), 0o600); err != nil {
90 t.Fatal(err)
91 }
92 snap, err := f.backend.SnapshotClaimLaunch(t.Context(), f.rt)
93 if err != nil {
94 t.Fatalf("snapshot: %v", err)
95 }
96 o, err := hostclaim.Read(t.Context(), f.claims, adminTokenHost)
97 if err != nil || o.Claim == nil || o.Claim.Holder == nil {
98 t.Fatalf("claim: %+v, %v", o, err)
99 }
100 h := o.Claim.Holder
101 sel := hostbox.Selection{Host: adminTokenHost, Captured: hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp}}
102 policy := claimInputPolicy(func(string) (*registry.Route, error) { return &f.rt, nil }, f.claims, nil, f.backend)
103 if err := policy(t.Context(), o.Claim, sel, snap.Identity); err != nil {
104 t.Fatalf("the policy refused the snapshot's own identity: %v", err)
105 }
106 changed := snap.Identity
107 changed.Config = "0000"
108 if err := policy(t.Context(), o.Claim, sel, changed); !errors.Is(err, hostbox.ErrChanged) {
109 t.Fatalf("another identity = %v, want ErrChanged", err)
110 }
111 if reads != 0 {
112 t.Fatalf("the policy read the bucket %d times", reads)
113 }
114 if _, err := claimServingLaunchSource(f.backend, f.tokens)(t.Context(), f.rt); !errors.Is(err, activator.ErrReplicaKey) || reads != 1 {
115 t.Fatalf("the launch source = %v after %d bucket reads, want ErrReplicaKey after one", err, reads)
116 }
117}
118
119// Final review I1: the claim path's backend is built by claimExecBackend
120// with the same readers the agent's backend gets, so a keyed replica
121// whose configured key is listed and still the bucket's starts on the
122// claim path (ruling 26), and a first wake asks the bucket before it mints
123// (ruling 5). The fixture's backend is not used: the readers are the ones
124// the production constructor wires, built from a box of this test's.
125func TestClaimBackendIsWiredWithTheKeyReaders(t *testing.T) {
126 homeKey := strings.Repeat("H", 43) + "="
127 f := newAdminTokenFixture(t, "box-1")
128 f.rt.Replica, f.rt.Account, f.rt.App = true, "bab", "jam"
129 bucket := release.NewDirStore(t.TempDir())
130 priv, _, err := appconfig.GenerateKey()
131 if err != nil {
132 t.Fatal(err)
133 }
134 if err := appconfig.SetEnv(t.Context(), bucket, "bab", "jam", map[string]string{instancekey.Var: homeKey}); err != nil {
135 t.Fatal(err)
136 }
137 list := []byte(adminTokenHost + "=" + instancekey.Digest(homeKey) + "\n")
138 keys := newInstanceKeyReaders(
139 func(k string) string {
140 if k == "CARLOS_SECRETS_KEY" {
141 return priv
142 }
143 return ""
144 },
145 func(context.Context) (release.Store, error) { return bucket, nil },
146 func() ([]byte, error) { return list, nil })
147 backend := claimExecBackend(ClaimRuntimeResources{DataDir: f.backend.DataDir, OptDir: f.backend.OptDir}, keys)
148 if backend.ReplicaHomeKeys == nil || backend.ConfiguredKey == nil {
149 t.Fatal("claimExecBackend left a reader nil")
150 }
151 if err := os.WriteFile(f.envPath(), []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("c", 64)+"\n"), 0o600); err != nil {
152 t.Fatal(err)
153 }
154 if err := os.WriteFile(filepath.Join(f.data, adminTokenHost+".config.env"), []byte(activator.InstanceKeyVar+"="+homeKey+"\n"), 0o600); err != nil {
155 t.Fatal(err)
156 }
157 launch := claimServingLaunchSource(backend, f.tokens)
158 got, err := launch(t.Context(), f.rt)
159 if err != nil {
160 t.Fatalf("a listed replica on the claim path: %v", err)
161 }
162 found := false
163 for _, kv := range got.Env {
164 found = found || kv == activator.InstanceKeyVar+"="+homeKey
165 }
166 if !found {
167 t.Fatal("the replica's launch does not deliver its home's key")
168 }
169 // The list no longer names it: refused, by the reader the constructor wired.
170 list = nil
171 if _, err := launch(t.Context(), f.rt); !errors.Is(err, activator.ErrReplicaKey) {
172 t.Fatalf("an unlisted replica = %v, want ErrReplicaKey", err)
173 }
174 // A first wake of a home row whose configuration sets a key this box
175 // has not delivered is deferred by the same backend (ruling 5).
176 f.rt.Replica = false
177 if err := os.Remove(filepath.Join(f.data, adminTokenHost+".config.env")); err != nil {
178 t.Fatal(err)
179 }
180 if _, err := launch(t.Context(), f.rt); !errors.Is(err, activator.ErrInstanceKeyDeferred) {
181 t.Fatalf("a first wake before delivery = %v, want ErrInstanceKeyDeferred", err)
182 }
183 prod := productionInstanceKeyReaders()
184 if prod.HomeKeys == nil || prod.Configured == nil {
185 t.Fatal("the production readers are not both built")
186 }
187}
188