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 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/instancekey"
13 "github.com/carlosframework/platform/internal/registry"
14)
15
16// Ruling 29 (Astra round 6 finding 1), deterministic: the launch reads a
17// configured key A, and then a config sync removes the key from this box's
18// <host>.config.env (the bucket no longer holds it) before the replica
19// gate runs. The gate decides on the layer the launch's own read found, so
20// it still consults the bucket, which now renders no key, and refuses. A
21// second read of the files would have found no key, skipped the bucket
22// and admitted a child that is handed A.
23func TestReplicaGateDecidesOnTheLaunchesOwnRead(t *testing.T) {
24 tok := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("c", 64) + "\n"
25 keyA := strings.Repeat("A", 43) + "="
26 b := testExecBackend(t)
27 rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true, Socket: noControlSocket(t)}
28 cfgPath := filepath.Join(b.DataDir, rt.Host+".config.env")
29 if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".env"), []byte(tok), 0o600); err != nil {
30 t.Fatal(err)
31 }
32 if err := os.WriteFile(cfgPath, []byte(InstanceKeyVar+"="+keyA+"\n"), 0o600); err != nil {
33 t.Fatal(err)
34 }
35 b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(keyA), true }
36 b.ConfiguredKey = func(context.Context, registry.Route) (string, error) { return "", nil } // the bucket: key removed
37 syncs := 0
38 instanceKeyHooks.replicaGate = func() {
39 syncs++
40 if err := os.WriteFile(cfgPath, []byte("OTHER=1\n"), 0o600); err != nil {
41 t.Error(err)
42 }
43 }
44 t.Cleanup(func() { instanceKeyHooks.replicaGate = nil })
45 out := stageHelperApp(t, b, rt.Host, "v1")
46 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
47 defer cancel()
48 proc, _, err := b.Start(ctx, rt)
49 if proc != nil {
50 defer proc.Stop(context.Background())
51 }
52 if syncs != 1 {
53 t.Fatalf("the sync ran %d times between the read and the gate, want once", syncs)
54 }
55 if got := outcomeOf(out); !errors.Is(err, ErrReplicaKey) || got != "" {
56 t.Fatalf("Start = %v, app outcome %q; want ErrReplicaKey and no app", err, got)
57 }
58}
59
60// The gate reads no env file: given the env and layer the launch's read
61// captured, it decides the same with the files gone.
62func TestReplicaGateReadsNoEnvFile(t *testing.T) {
63 keyA := strings.Repeat("A", 43) + "="
64 b := &ExecBackend{DataDir: t.TempDir()}
65 rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true}
66 if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".config.env"), []byte(InstanceKeyVar+"="+keyA+"\n"), 0o600); err != nil {
67 t.Fatal(err)
68 }
69 env, layer, err := b.instanceEnvLayer(rt)
70 if err != nil || layer != instancekey.LayerConfig || env[InstanceKeyVar] != keyA {
71 t.Fatalf("instanceEnvLayer = %v, %v", layer, err)
72 }
73 if err := os.RemoveAll(b.DataDir); err != nil {
74 t.Fatal(err)
75 }
76 b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(keyA), true }
77 for _, tc := range []struct {
78 bucket string
79 ok bool
80 }{{keyA, true}, {"", false}} {
81 b.ConfiguredKey = func(context.Context, registry.Route) (string, error) {
82 if tc.bucket == "" {
83 return "", nil
84 }
85 return instancekey.Digest(tc.bucket), nil
86 }
87 if err := b.replicaKeyGate(rt, env, layer, false); (err == nil) != tc.ok {
88 t.Fatalf("bucket %q: gate = %v, want ok %v", tc.bucket, err, tc.ok)
89 }
90 }
91 if err := b.replicaKeyGate(rt, env, instancekey.LayerNone, false); !errors.Is(err, ErrReplicaKey) {
92 t.Fatalf("a key with no known layer = %v, want a refusal", err)
93 }
94}
95