carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
4 "context"
5 "errors"
6 "net"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/instancectl"
14 "github.com/carlosframework/platform/internal/instancekey"
15 "github.com/carlosframework/platform/internal/provision"
16 "github.com/carlosframework/platform/internal/registry"
17)
18
19// socketsAllowed reports whether this process may bind a unix socket (the
20// sandbox refuses it).
21func socketsAllowed(t *testing.T) bool {
22 dir, err := os.MkdirTemp("", "ik") // short: a test's own temp dir can exceed a socket path's limit
23 if err != nil {
24 t.Fatal(err)
25 }
26 defer os.RemoveAll(dir)
27 l, err := net.Listen("unix", filepath.Join(dir, "p.sock"))
28 if err != nil {
29 return false
30 }
31 l.Close()
32 return true
33}
34
35// Ruling 26 with a control socket: a keyless replica runs, and the app's
36// helper refuses at step 3 (ErrNoInstanceKey) instead of minting.
37func TestKeylessReplicaWithAControlSocketLeavesTheHelperToRefuse(t *testing.T) {
38 if !socketsAllowed(t) {
39 t.Skip("needs unix sockets (run with the sandbox off)")
40 }
41 b := testExecBackend(t)
42 rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true, Socket: tmpSock(t)}
43 if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".env"), []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("c", 64)+"\n"), 0o600); err != nil {
44 t.Fatal(err)
45 }
46 out := stageHelperApp(t, b, rt.Host, "v1")
47 ctx, cancel := recordedCtx(2*time.Second, out)
48 defer cancel()
49 proc, _, _ := b.Start(ctx, rt)
50 if proc != nil {
51 defer proc.Stop(context.Background())
52 }
53 if got := outcomeOf(out); got != "refused" {
54 t.Fatalf("app outcome %q, want the helper's refusal", got)
55 }
56}
57
58// Ruling 26 on the nested layout: the seed role has no control socket, so
59// a keyless replica's seed is refused at prepare; a run role handed a key
60// that is not the home's is refused too; one handed the home's key is
61// prepared with it. The bucket's fresh read renders the case's own
62// configured key, so the "another key" refusal is the home-key digest
63// check's alone, not the fresh read's.
64func TestNestedReplicaPrepareIsGated(t *testing.T) {
65 homeKey := strings.Repeat("H", 43) + "="
66 for _, tc := range []struct {
67 name, config string
68 bucket string // the key the bucket's configuration renders now
69 role instancectl.Role
70 refused bool
71 }{
72 {"a keyless seed", "", "", instancectl.RoleSeed, true},
73 {"a run role with another key", InstanceKeyVar + "=configured-B\n", "configured-B", instancectl.RoleRun, true},
74 {"a run role with the home's key", InstanceKeyVar + "=" + homeKey + "\n", homeKey, instancectl.RoleRun, false},
75 } {
76 t.Run(tc.name, func(t *testing.T) {
77 b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{})
78 rt.Replica = true
79 b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(homeKey), true }
80 b.ConfiguredKey = func(context.Context, registry.Route) (string, error) {
81 if tc.bucket == "" {
82 return "", nil
83 }
84 return instancekey.Digest(tc.bucket), nil
85 }
86 dir := provision.InstanceDir(b.DataDir, rt.Host)
87 if err := os.MkdirAll(dir, 0o700); err != nil {
88 t.Fatal(err)
89 }
90 if tc.config != "" {
91 if err := os.WriteFile(filepath.Join(dir, rt.Host+".config.env"), []byte(tc.config), 0o600); err != nil {
92 t.Fatal(err)
93 }
94 }
95 _, ctl, logs, env, err := b.prepare(rt, tc.role)
96 if tc.refused {
97 if !errors.Is(err, ErrReplicaKey) {
98 t.Fatalf("prepare = %v, want ErrReplicaKey", err)
99 }
100 return
101 }
102 if err != nil {
103 t.Fatalf("prepare: %v", err)
104 }
105 logs.Close()
106 if ctl != nil {
107 ctl.Close()
108 }
109 if env[InstanceKeyVar] != homeKey {
110 t.Fatal("the prepared env does not carry the home's key")
111 }
112 })
113 }
114}
115
116// The nested legacy sidecar role is gated as the flat one is: a key in its
117// own file must be the home's, and a keyless one is refused (it has no
118// control socket).
119func TestNestedReplicaSidecarPrepareIsGated(t *testing.T) {
120 homeKey := strings.Repeat("H", 43) + "="
121 for _, tc := range []struct {
122 name, sidecar string
123 refused bool
124 }{
125 {"a keyless sidecar", "OTHER=1\n", true},
126 {"a sidecar with another key", InstanceKeyVar + "=configured-B\n", true},
127 {"a sidecar with the home's key", InstanceKeyVar + "=" + homeKey + "\n", false},
128 } {
129 t.Run(tc.name, func(t *testing.T) {
130 b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{})
131 rt.Replica = true
132 b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(homeKey), true }
133 b.ConfiguredKey = func(context.Context, registry.Route) (string, error) { return instancekey.Digest("configured-B"), nil }
134 dir := provision.InstanceDir(b.DataDir, rt.Host)
135 if err := os.MkdirAll(dir, 0o700); err != nil {
136 t.Fatal(err)
137 }
138 if err := os.WriteFile(filepath.Join(dir, rt.Host+"-sidecar.env"), []byte(tc.sidecar), 0o600); err != nil {
139 t.Fatal(err)
140 }
141 _, ctl, logs, env, err := b.prepare(rt, instancectl.RoleSidecar)
142 if tc.refused {
143 if !errors.Is(err, ErrReplicaKey) {
144 t.Fatalf("prepare = %v, want ErrReplicaKey", err)
145 }
146 return
147 }
148 if err != nil {
149 t.Fatalf("prepare: %v", err)
150 }
151 logs.Close()
152 if ctl != nil {
153 ctl.Close()
154 }
155 if env[InstanceKeyVar] != homeKey {
156 t.Fatal("the prepared sidecar env does not carry the home's key")
157 }
158 })
159 }
160}
161
162// A refused replica launch closes the control listener it had bound: the
163// socket must not outlive the launch it was bound for, on the flat spawn or
164// the nested run role.
165func TestReplicaRefusalClosesTheControlListener(t *testing.T) {
166 if !socketsAllowed(t) {
167 t.Skip("needs unix sockets (run with the sandbox off)")
168 }
169 homeKey := strings.Repeat("H", 43) + "="
170 homes := func(string) (string, bool) { return instancekey.Digest(homeKey), true }
171 bucketB := func(context.Context, registry.Route) (string, error) { return instancekey.Digest("configured-B"), nil }
172 closed := func(t *testing.T, control string) {
173 t.Helper()
174 if c, err := net.Dial("unix", control); err == nil {
175 c.Close()
176 t.Fatalf("the control socket %s still accepts after the refusal", control)
177 }
178 }
179 t.Run("flat spawn", func(t *testing.T) {
180 b := testExecBackend(t)
181 rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true, Socket: tmpSock(t)}
182 if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".env"), []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("c", 64)+"\n"), 0o600); err != nil {
183 t.Fatal(err)
184 }
185 if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".config.env"), []byte(InstanceKeyVar+"=configured-B\n"), 0o600); err != nil {
186 t.Fatal(err)
187 }
188 b.ReplicaHomeKeys, b.ConfiguredKey = homes, bucketB
189 out := stageHelperApp(t, b, rt.Host, "v1")
190 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
191 defer cancel()
192 proc, _, err := b.Start(ctx, rt)
193 if proc != nil {
194 defer proc.Stop(context.Background())
195 }
196 if !errors.Is(err, ErrReplicaKey) || outcomeOf(out) != "" {
197 t.Fatalf("Start = %v; want ErrReplicaKey and no app", err)
198 }
199 closed(t, rt.Socket+".control")
200 })
201 t.Run("nested run role", func(t *testing.T) {
202 b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{})
203 rt.Replica = true
204 b.ReplicaHomeKeys, b.ConfiguredKey = homes, bucketB
205 dir := provision.InstanceDir(b.DataDir, rt.Host)
206 if err := os.MkdirAll(dir, 0o700); err != nil {
207 t.Fatal(err)
208 }
209 if err := os.WriteFile(filepath.Join(dir, rt.Host+".config.env"), []byte(InstanceKeyVar+"=configured-B\n"), 0o600); err != nil {
210 t.Fatal(err)
211 }
212 if _, _, _, _, err := b.prepare(rt, instancectl.RoleRun); !errors.Is(err, ErrReplicaKey) {
213 t.Fatalf("prepare = %v, want ErrReplicaKey", err)
214 }
215 closed(t, rt.Socket+".control")
216 })
217}
218