| 1 | package activator |
| 2 | |
| 3 | import ( |
| 4 | "log" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | "time" |
| 10 | ) |
| 11 | |
| 12 | func TestEnsureAdminTokenMintsOnce(t *testing.T) { |
| 13 | p := filepath.Join(t.TempDir(), "h.env") |
| 14 | if err := EnsureAdminToken(p); err != nil { |
| 15 | t.Fatal(err) |
| 16 | } |
| 17 | st, _ := os.Stat(p) |
| 18 | if st.Mode().Perm() != 0o600 { |
| 19 | t.Fatalf("mode = %o", st.Mode().Perm()) |
| 20 | } |
| 21 | env, _ := readEnvFile(p) |
| 22 | tok := env["CARLOS_ADMIN_TOKEN"] |
| 23 | if len(tok) != 64 { |
| 24 | t.Fatalf("token = %q", tok) |
| 25 | } |
| 26 | if err := EnsureAdminToken(p); err != nil { |
| 27 | t.Fatal(err) |
| 28 | } |
| 29 | env, _ = readEnvFile(p) |
| 30 | if env["CARLOS_ADMIN_TOKEN"] != tok { |
| 31 | t.Fatal("token rotated on second call") |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | func TestEnsureAdminTokenPreservesOtherVars(t *testing.T) { |
| 36 | p := filepath.Join(t.TempDir(), "h.env") |
| 37 | os.WriteFile(p, []byte("DATABASE_URL=sqlite\n# comment\n"), 0o600) |
| 38 | if err := EnsureAdminToken(p); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | raw, _ := os.ReadFile(p) |
| 42 | if !strings.HasPrefix(string(raw), "DATABASE_URL=sqlite\n# comment\n") || !strings.Contains(string(raw), "CARLOS_ADMIN_TOKEN=") { |
| 43 | t.Fatalf("file = %q", raw) |
| 44 | } |
| 45 | env, _ := readEnvFile(p) |
| 46 | if env["DATABASE_URL"] != "sqlite" { |
| 47 | t.Fatal("other var lost") |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // A looser-mode file that already exists is tightened by the mint: O_CREATE's |
| 52 | // 0600 only ever applied to a file created here, and a 0644 file now holds a |
| 53 | // secret every other UNIX user on the box could read. |
| 54 | func TestEnsureAdminTokenTightensAnExistingFile(t *testing.T) { |
| 55 | p := filepath.Join(t.TempDir(), "h.env") |
| 56 | os.WriteFile(p, []byte("DATABASE_URL=sqlite\n"), 0o644) |
| 57 | if err := EnsureAdminToken(p); err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | st, _ := os.Stat(p) |
| 61 | if st.Mode().Perm() != 0o600 { |
| 62 | t.Fatalf("mode after mint = %o, want 600", st.Mode().Perm()) |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | // The tightening is not the minting path's alone. A file that ALREADY held a |
| 67 | // token took an early return that skipped the chmod entirely, so a 0644 env |
| 68 | // file written by hand (or by an older tool) kept holding the instance's |
| 69 | // secret at world-readable mode for as long as the instance lived — the mode |
| 70 | // only ever converged for files this function itself created. |
| 71 | func TestEnsureAdminTokenTightensAFileThatAlreadyHoldsAToken(t *testing.T) { |
| 72 | p := filepath.Join(t.TempDir(), "h.env") |
| 73 | tok := strings.Repeat("ab", 32) |
| 74 | if err := os.WriteFile(p, []byte("CARLOS_ADMIN_TOKEN="+tok+"\n"), 0o644); err != nil { |
| 75 | t.Fatal(err) |
| 76 | } |
| 77 | if err := EnsureAdminToken(p); err != nil { |
| 78 | t.Fatal(err) |
| 79 | } |
| 80 | st, err := os.Stat(p) |
| 81 | if err != nil { |
| 82 | t.Fatal(err) |
| 83 | } |
| 84 | if st.Mode().Perm() != 0o600 { |
| 85 | t.Fatalf("mode of a pre-existing token file after ensure = %o, want 600", st.Mode().Perm()) |
| 86 | } |
| 87 | env, _ := readEnvFile(p) |
| 88 | if env["CARLOS_ADMIN_TOKEN"] != tok { |
| 89 | t.Fatalf("token = %q, want the pre-existing %q — tightening must not rewrite the secret", env["CARLOS_ADMIN_TOKEN"], tok) |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // captureLog redirects the standard logger for the duration of one test and |
| 94 | // returns what was written to it. Assertions below count a marker substring |
| 95 | // rather than compare whole buffers: a goroutine left running by an earlier |
| 96 | // test in this package (the activator's sweeper logs) can drop a line in |
| 97 | // here at any moment, and a test that fails on someone else's log line is a |
| 98 | // flake, not a finding. |
| 99 | func captureLog(t *testing.T) *strings.Builder { |
| 100 | t.Helper() |
| 101 | var buf strings.Builder |
| 102 | flags := log.Flags() |
| 103 | log.SetOutput(&buf) |
| 104 | log.SetFlags(0) |
| 105 | t.Cleanup(func() { |
| 106 | log.SetOutput(os.Stderr) |
| 107 | log.SetFlags(flags) |
| 108 | }) |
| 109 | return &buf |
| 110 | } |
| 111 | |
| 112 | // The ruling (2026-08-24): a malformed existing token is WARNED about and |
| 113 | // LEFT IN PLACE — never rotated. The running instance was handed this same |
| 114 | // value at spawn, so file and process agree and its ticks authenticate; |
| 115 | // re-minting behind its back would 401 a live instance to repair a string. |
| 116 | // The warning carries the operator's own repair instead. |
| 117 | func TestEnsureAdminTokenWarnsAboutAMalformedTokenAndLeavesIt(t *testing.T) { |
| 118 | p := filepath.Join(t.TempDir(), "h.env") |
| 119 | const body = "DATABASE_URL=sqlite\nCARLOS_ADMIN_TOKEN=deadbe\n" |
| 120 | if err := os.WriteFile(p, []byte(body), 0o600); err != nil { |
| 121 | t.Fatal(err) |
| 122 | } |
| 123 | buf := captureLog(t) |
| 124 | if err := EnsureAdminToken(p); err != nil { |
| 125 | t.Fatal(err) |
| 126 | } |
| 127 | raw, err := os.ReadFile(p) |
| 128 | if err != nil { |
| 129 | t.Fatal(err) |
| 130 | } |
| 131 | if string(raw) != body { |
| 132 | t.Fatalf("file after ensure = %q, want it byte-identical to %q — a malformed token is never rotated", raw, body) |
| 133 | } |
| 134 | got := buf.String() |
| 135 | if !strings.Contains(got, "CARLOS_ADMIN_TOKEN") || !strings.Contains(got, p) { |
| 136 | t.Fatalf("log = %q, want a warning naming the variable and the file", got) |
| 137 | } |
| 138 | if !strings.Contains(got, "left in place") { |
| 139 | t.Fatalf("log = %q, want it to say the value was left in place", got) |
| 140 | } |
| 141 | if n := strings.Count(got, "not the shape"); n != 1 { |
| 142 | t.Fatalf("warning appeared %d times, want 1: %q", n, got) |
| 143 | } |
| 144 | // Sticky condition, repeated caller: the runner ensures the token on every |
| 145 | // tick for the host. One line per file per process, not one per tick. |
| 146 | if err := EnsureAdminToken(p); err != nil { |
| 147 | t.Fatal(err) |
| 148 | } |
| 149 | if n := strings.Count(buf.String(), "not the shape"); n != 1 { |
| 150 | t.Fatalf("warning appeared %d times across two ensures, want 1: %q", n, buf.String()) |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | // The other half of the same contract: a well-formed token is silent. A |
| 155 | // warning that fired on healthy files would be noise on every box, and the |
| 156 | // shape check exists to be believed. |
| 157 | func TestEnsureAdminTokenIsSilentForAWellFormedToken(t *testing.T) { |
| 158 | p := filepath.Join(t.TempDir(), "h.env") |
| 159 | if err := EnsureAdminToken(p); err != nil { // mint one, so the shape is by construction ours |
| 160 | t.Fatal(err) |
| 161 | } |
| 162 | before, err := os.ReadFile(p) |
| 163 | if err != nil { |
| 164 | t.Fatal(err) |
| 165 | } |
| 166 | buf := captureLog(t) |
| 167 | if err := EnsureAdminToken(p); err != nil { |
| 168 | t.Fatal(err) |
| 169 | } |
| 170 | after, err := os.ReadFile(p) |
| 171 | if err != nil { |
| 172 | t.Fatal(err) |
| 173 | } |
| 174 | if string(after) != string(before) { |
| 175 | t.Fatalf("file changed under a well-formed token: %q → %q", before, after) |
| 176 | } |
| 177 | if strings.Contains(buf.String(), "CARLOS_ADMIN_TOKEN") || strings.Contains(buf.String(), "not the shape") { |
| 178 | t.Fatalf("log = %q, want nothing said about a healthy token", buf.String()) |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | // The shape is the minting format, exactly: 64 lowercase hex characters. |
| 183 | func TestWellFormedAdminToken(t *testing.T) { |
| 184 | good := strings.Repeat("0f", 32) |
| 185 | for _, c := range []struct { |
| 186 | name string |
| 187 | tok string |
| 188 | want bool |
| 189 | }{ |
| 190 | {"minted shape", good, true}, |
| 191 | {"empty", "", false}, |
| 192 | {"short", good[:63], false}, |
| 193 | {"long", good + "0", false}, |
| 194 | {"uppercase hex", strings.ToUpper(good), false}, |
| 195 | {"non-hex", strings.Repeat("z", 64), false}, |
| 196 | {"quoted", `"` + good[:62] + `"`, false}, |
| 197 | } { |
| 198 | if got := wellFormedAdminToken(c.tok); got != c.want { |
| 199 | t.Errorf("%s: wellFormedAdminToken(%q) = %v, want %v", c.name, c.tok, got, c.want) |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | // os.Chmod is path-based chmod(2) and follows symlinks, and this now runs on |
| 205 | // every tick for the host rather than once per instance lifetime. A same-uid |
| 206 | // sibling — the residual spec §7.2 documents — that swaps <host>.env for a |
| 207 | // link must not turn the agent's tightening into a mode change somewhere |
| 208 | // else on the box. Same guard backend_exec.go puts in front of the instance |
| 209 | // socket's normalization, for the same reason. |
| 210 | func TestEnsureAdminTokenRefusesToChmodThroughASymlink(t *testing.T) { |
| 211 | dir := t.TempDir() |
| 212 | target := filepath.Join(dir, "someone-elses-file") |
| 213 | if err := os.WriteFile(target, []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("ab", 32)+"\n"), 0o644); err != nil { |
| 214 | t.Fatal(err) |
| 215 | } |
| 216 | link := filepath.Join(dir, "h.env") |
| 217 | if err := os.Symlink(target, link); err != nil { |
| 218 | t.Skipf("this filesystem will not make symlinks: %v", err) |
| 219 | } |
| 220 | buf := captureLog(t) |
| 221 | if err := EnsureAdminToken(link); err != nil { |
| 222 | t.Fatal(err) |
| 223 | } |
| 224 | st, err := os.Stat(target) |
| 225 | if err != nil { |
| 226 | t.Fatal(err) |
| 227 | } |
| 228 | if st.Mode().Perm() != 0o644 { |
| 229 | t.Fatalf("mode of the symlink's TARGET = %o, want the untouched 644 — the agent must not chmod through a link", st.Mode().Perm()) |
| 230 | } |
| 231 | if !strings.Contains(buf.String(), "SYMLINK") { |
| 232 | t.Fatalf("log = %q, want the refusal named", buf.String()) |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | // The throttle is a clock, not a latch: the chmod is retried every tick, so |
| 237 | // the condition can come back (an operator fixes a 0644 file, something |
| 238 | // re-widens it weeks later) and a once-per-process bool would keep the agent |
| 239 | // silent about a world-readable secret until the next binary roll. |
| 240 | func TestEnsureAdminTokenComplaintIsThrottledNotSilenced(t *testing.T) { |
| 241 | p := filepath.Join(t.TempDir(), "h.env") |
| 242 | if err := os.WriteFile(p, []byte("CARLOS_ADMIN_TOKEN=deadbe\n"), 0o600); err != nil { |
| 243 | t.Fatal(err) |
| 244 | } |
| 245 | buf := captureLog(t) |
| 246 | for i := 0; i < 3; i++ { |
| 247 | if err := EnsureAdminToken(p); err != nil { |
| 248 | t.Fatal(err) |
| 249 | } |
| 250 | } |
| 251 | if n := strings.Count(buf.String(), "not the shape"); n != 1 { |
| 252 | t.Fatalf("warning appeared %d times inside the throttle window, want 1", n) |
| 253 | } |
| 254 | // Age the memory past the window — what a real recurrence looks like. |
| 255 | adminTokenMu.Lock() |
| 256 | for k := range adminTokenLogged { |
| 257 | adminTokenLogged[k] = time.Now().Add(-adminTokenLogRepeat - time.Minute) |
| 258 | } |
| 259 | adminTokenMu.Unlock() |
| 260 | if err := EnsureAdminToken(p); err != nil { |
| 261 | t.Fatal(err) |
| 262 | } |
| 263 | if n := strings.Count(buf.String(), "not the shape"); n != 2 { |
| 264 | t.Fatalf("warning appeared %d times after the window elapsed, want it said again (2)", n) |
| 265 | } |
| 266 | } |
| 267 | |