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 "errors"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9 "time"
10
11 "golang.org/x/sys/unix"
12
13 "github.com/carlosframework/platform/internal/instancectl"
14 "github.com/carlosframework/platform/internal/provision"
15 "github.com/carlosframework/platform/internal/registry"
16)
17
18// The nested prepare mints the key into the instance directory's own env
19// file and hands it to the role in the same env it returns: the run role
20// and the seed role (coverage, Astra round 1).
21func TestPrepareMintsTheInstanceKeyIntoTheNestedEnvFile(t *testing.T) {
22 for _, role := range []instancectl.Role{instancectl.RoleRun, instancectl.RoleSeed} {
23 t.Run(string(role), func(t *testing.T) {
24 b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{})
25 _, ctl, logs, env, err := b.prepare(rt, role)
26 if err != nil {
27 t.Fatalf("prepare: %v", err)
28 }
29 logs.Close()
30 if ctl != nil {
31 ctl.Close()
32 }
33 own, err := readEnvFileBeneath(provision.InstanceDir(b.DataDir, rt.Host), rt.Host+".env")
34 if err != nil {
35 t.Fatal(err)
36 }
37 if !wellFormedInstanceKey(own[InstanceKeyVar]) || env[InstanceKeyVar] != own[InstanceKeyVar] {
38 t.Fatal("prepare's env does not carry the key minted into the nested env file")
39 }
40 })
41 }
42}
43
44// Finding 3: the nested backfill opens an existing file with mode 0
45// (openat2 refuses a mode without O_CREAT), appends, and sets 0600 on the
46// descriptor.
47func TestBackfillInstanceKeyAppendsToANestedEnvFile(t *testing.T) {
48 b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{})
49 dir := provision.InstanceDir(b.DataDir, rt.Host)
50 if err := os.MkdirAll(dir, 0o700); err != nil {
51 t.Fatal(err)
52 }
53 envPath := filepath.Join(dir, rt.Host+".env")
54 tok := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("e", 64) + "\n"
55 if err := os.WriteFile(envPath, []byte(tok), 0o644); err != nil {
56 t.Fatal(err)
57 }
58 if got, err := b.BackfillInstanceKey(t.Context(), rt); err != nil || got != InstanceKeyMinted {
59 t.Fatalf("nested backfill = %v, %v; want minted", got, err)
60 }
61 raw, _ := os.ReadFile(envPath)
62 if !strings.HasPrefix(string(raw), tok) || len(keyLines(raw)) != 1 || !wellFormedInstanceKey(keyLines(raw)[0]) {
63 t.Fatalf("nested env file after the backfill: %d bytes, %d keys", len(raw), len(keyLines(raw)))
64 }
65 if st, _ := os.Stat(envPath); st.Mode().Perm() != 0o600 {
66 t.Fatalf("mode = %o, want 600", st.Mode().Perm())
67 }
68 if got, err := b.BackfillInstanceKey(t.Context(), rt); err != nil || got != InstanceKeyPresent {
69 t.Fatalf("second nested backfill = %v, %v; want present", got, err)
70 }
71 if raw2, _ := os.ReadFile(envPath); string(raw2) != string(raw) {
72 t.Fatal("the second backfill changed the file")
73 }
74}
75
76// A nested own env file is on tenant-writable ground: a symlink planted at
77// its name is refused, and the file it points at is not touched.
78func TestEnsureInstanceKeyBeneathRefusesASymlink(t *testing.T) {
79 dir := t.TempDir()
80 target := filepath.Join(t.TempDir(), "elsewhere.env")
81 if err := os.WriteFile(target, []byte("X=1\n"), 0o644); err != nil {
82 t.Fatal(err)
83 }
84 if err := os.Symlink(target, filepath.Join(dir, "shop.example.com.env")); err != nil {
85 t.Fatal(err)
86 }
87 if _, err := ensureInstanceKeyBeneathNoConfig(dir, "shop.example.com.env"); err == nil {
88 t.Fatal("ensure followed a symlink planted at the env file's name")
89 }
90 if raw, _ := os.ReadFile(target); string(raw) != "X=1\n" {
91 t.Fatalf("the symlink's target changed: %q", raw)
92 }
93}
94
95// Review focus 4 and the final review's I3: a FIFO planted at an env
96// file's name fails the backfill fast and is refused; it never blocks
97// agent start. The nested row's files are opened through instancefs; the
98// flat row's own file and its rendered configuration through
99// openOwnEnvNoFollow, which opens O_NONBLOCK and accepts only a regular
100// file. The startup backfill runs under adminTokenMu before the activator
101// starts, so a blocking open here would keep every instance on the box
102// from waking.
103func TestBackfillInstanceKeyRefusesAFIFOWithoutBlocking(t *testing.T) {
104 for _, tc := range []struct {
105 name string
106 flat bool
107 file string // the file the FIFO is planted at, relative to the row's directory
108 }{
109 {"nested own file", false, ".env"},
110 {"flat own file", true, ".env"},
111 {"flat configuration", true, ".config.env"},
112 } {
113 t.Run(tc.name, func(t *testing.T) {
114 var b *ExecBackend
115 var rt registry.Route
116 var dir string
117 if tc.flat {
118 b = testExecBackend(t)
119 rt = registry.Route{Host: "shop.example.com", Kind: "instance"}
120 dir = b.DataDir
121 // A flat own file exists, so the backfill reaches the
122 // configuration read when the FIFO is the configuration.
123 if tc.file != ".env" {
124 if err := os.WriteFile(filepath.Join(dir, rt.Host+".env"), []byte("A=1\n"), 0o600); err != nil {
125 t.Fatal(err)
126 }
127 }
128 } else {
129 b, rt = nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{})
130 dir = provision.InstanceDir(b.DataDir, rt.Host)
131 if err := os.MkdirAll(dir, 0o700); err != nil {
132 t.Fatal(err)
133 }
134 }
135 if err := unix.Mkfifo(filepath.Join(dir, rt.Host+tc.file), 0o600); err != nil {
136 t.Fatal(err)
137 }
138 done := make(chan error, 1)
139 go func() {
140 _, err := b.BackfillInstanceKey(t.Context(), rt)
141 done <- err
142 }()
143 select {
144 case err := <-done:
145 if err == nil {
146 t.Fatal("backfill over a FIFO succeeded, want an error")
147 }
148 if tc.flat && !errors.Is(err, errOwnEnvNotRegular) {
149 t.Fatalf("backfill over a flat FIFO = %v, want errOwnEnvNotRegular", err)
150 }
151 case <-time.After(2 * time.Second):
152 t.Fatal("backfill blocked on a FIFO")
153 }
154 if tc.file == ".env" {
155 return // the own file is the FIFO: nothing to read back
156 }
157 if raw, err := os.ReadFile(filepath.Join(dir, rt.Host+".env")); err != nil || string(raw) != "A=1\n" {
158 t.Fatalf("the own file changed beside a refused configuration: %q, %v", raw, err)
159 }
160 })
161 }
162}
163