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
11 "github.com/carlosframework/platform/internal/provision"
12 "github.com/carlosframework/platform/internal/registry"
13)
14
15// Finding 2 on the claim path: when the token and the key are already
16// present, EnsureClaimAdminToken writes nothing and calls no authorizer,
17// but it does not answer until the env file and its directory are synced.
18// A file that is not 0600 goes through the authorized write, which sets it.
19func TestEnsureClaimAdminTokenSyncsAPresentKeyBeforeItAnswers(t *testing.T) {
20 probe := probeInstanceKeySync(t)
21 const host = "claim-key.example"
22 b := &ExecBackend{DataDir: t.TempDir()}
23 rt := registry.Route{Host: host, Socket: "/run/carlos/claim-key.sock", DBPath: "/data/claim-key.db"}
24 envPath := filepath.Join(b.DataDir, host+".env")
25 body := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("d", 64) + "\n" + InstanceKeyVar + "=" + strings.Repeat("A", 43) + "=\n"
26 if err := os.WriteFile(envPath, []byte(body), 0o600); err != nil {
27 t.Fatal(err)
28 }
29 authorized := 0
30 authorize := func(_ context.Context, write func() error) error { authorized++; return write() }
31 for _, fail := range []func(error){func(e error) { probe.fileErr = e }, func(e error) { probe.dirErr = e }} {
32 fail(errors.New("injected: EIO"))
33 if err := b.EnsureClaimAdminToken(context.Background(), rt, authorize); err == nil {
34 t.Fatal("the launch source answered while the env file's sync fails")
35 }
36 fail(nil)
37 }
38 before := *probe
39 if err := b.EnsureClaimAdminToken(context.Background(), rt, authorize); err != nil {
40 t.Fatal(err)
41 }
42 if authorized != 0 || probe.file != before.file+1 || probe.dir != before.dir+1 {
43 t.Fatalf("authorized %d, synced file %d dir %d; want 0, 1, 1", authorized, probe.file-before.file, probe.dir-before.dir)
44 }
45 if err := os.Chmod(envPath, 0o644); err != nil {
46 t.Fatal(err)
47 }
48 if err := b.EnsureClaimAdminToken(context.Background(), rt, authorize); err != nil {
49 t.Fatal(err)
50 }
51 if st, _ := os.Stat(envPath); authorized != 1 || st.Mode().Perm() != 0o600 {
52 t.Fatalf("a 0644 file: authorized %d, mode %o; want the guarded path to set 600", authorized, st.Mode().Perm())
53 }
54 if raw, _ := os.ReadFile(envPath); string(raw) != body {
55 t.Fatal("the claim path rewrote the env file")
56 }
57}
58
59// Pre-flight ruling A: the early return never fails a launch that succeeds
60// today. With no <host>.env at all and both the token and the key supplied
61// by the rendered configuration, there is no file to make durable (a
62// configured key is exempt, ruling 5, and an absent own file is ruling 7's
63// case), so the launch source answers as it did before: no open failure, no
64// authorizer, and no file created.
65func TestEnsureClaimAdminTokenProceedsWithNoOwnEnvFileWhenConfigSuppliesBoth(t *testing.T) {
66 const host = "claim-cfg.example"
67 b := &ExecBackend{DataDir: t.TempDir()}
68 rt := registry.Route{Host: host, Socket: "/run/carlos/claim-cfg.sock", DBPath: "/data/claim-cfg.db"}
69 cfg := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("d", 64) + "\n" + InstanceKeyVar + "=configured\n"
70 if err := os.WriteFile(filepath.Join(b.DataDir, host+".config.env"), []byte(cfg), 0o600); err != nil {
71 t.Fatal(err)
72 }
73 authorized := 0
74 authorize := func(_ context.Context, write func() error) error { authorized++; return write() }
75 if err := b.EnsureClaimAdminToken(context.Background(), rt, authorize); err != nil {
76 t.Fatalf("a launch that succeeds today failed: %v", err)
77 }
78 if _, err := os.Stat(filepath.Join(b.DataDir, host+".env")); !errors.Is(err, os.ErrNotExist) || authorized != 0 {
79 t.Fatalf("the early return wrote: authorized %d, own env file: %v", authorized, err)
80 }
81}
82
83// The guarded write mints only what the effective env lacks. A token the
84// rendered configuration supplies is present (the launch delivers it), so
85// the guarded path mints the key alone and does not put a second token
86// into the own file above it: only a missing token is minted, as the
87// function has always promised.
88func TestEnsureClaimAdminTokenMintsOnlyTheKeyWhenConfigSuppliesTheToken(t *testing.T) {
89 const host = "claim-cfgtok.example"
90 b := &ExecBackend{DataDir: t.TempDir()}
91 rt := registry.Route{Host: host, Socket: "/run/carlos/claim-cfgtok.sock", DBPath: "/data/claim-cfgtok.db"}
92 tok := strings.Repeat("d", 64)
93 if err := os.WriteFile(filepath.Join(b.DataDir, host+".config.env"), []byte("CARLOS_ADMIN_TOKEN="+tok+"\n"), 0o600); err != nil {
94 t.Fatal(err)
95 }
96 authorized := 0
97 authorize := func(_ context.Context, write func() error) error { authorized++; return write() }
98 if err := b.EnsureClaimAdminToken(context.Background(), rt, authorize); err != nil {
99 t.Fatal(err)
100 }
101 raw, err := os.ReadFile(filepath.Join(b.DataDir, host+".env"))
102 if err != nil {
103 t.Fatalf("the guarded path minted no key: %v", err)
104 }
105 own := parseEnvBytes(raw)
106 if authorized != 1 || !wellFormedInstanceKey(own[InstanceKeyVar]) || own["CARLOS_ADMIN_TOKEN"] != "" {
107 t.Fatalf("authorized %d, own file %d bytes, key well formed %v, token present %v; want one guarded write with a key and no token", authorized, len(raw), wellFormedInstanceKey(own[InstanceKeyVar]), own["CARLOS_ADMIN_TOKEN"] != "")
108 }
109 vars, err := b.instanceEnv(rt)
110 if err != nil || vars["CARLOS_ADMIN_TOKEN"] != tok {
111 t.Fatalf("the launch's token changed: %q, %v", vars["CARLOS_ADMIN_TOKEN"], err)
112 }
113}
114
115// Task 6 ruling (K2): the claim launch source is the one launch path that
116// returns the key's refusal without a wrapper of its own, so it names the
117// host there, once. A fenced or conflicting row is refused under the guard,
118// the error carries "instance key for <host>", and the file is untouched.
119func TestEnsureClaimAdminTokenNamesTheHostOnceInAKeyRefusal(t *testing.T) {
120 for _, layout := range []Layout{LayoutFenced, LayoutConflict} {
121 t.Run(string(layout), func(t *testing.T) {
122 b := testExecBackend(t)
123 const host = "shop.example.com"
124 rt := registry.Route{Host: host, Kind: "instance", Hibernate: true,
125 DBPath: provision.DefaultDBPath(b.DataDir, host), Socket: filepath.Join(provision.SocketDir, host+".sock")}
126 own := filepath.Join(b.DataDir, host+".env")
127 tok := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("d", 64) + "\n"
128 if err := os.WriteFile(own, []byte(tok), 0o600); err != nil {
129 t.Fatal(err)
130 }
131 switch layout {
132 case LayoutFenced:
133 b.LayoutFacts.Journal = func(string) bool { return true }
134 case LayoutConflict:
135 b.LayoutFacts.SameID = func(a, b string) bool { return true }
136 b.LayoutFacts.Peers = func() ([]registry.Route, error) {
137 return []registry.Route{{Host: "other.example.com", Kind: "instance", Hibernate: true, Socket: "/nonexistent/other.sock"}}, nil
138 }
139 }
140 authorized := 0
141 authorize := func(_ context.Context, write func() error) error { authorized++; return write() }
142 err := b.EnsureClaimAdminToken(context.Background(), rt, authorize)
143 if err == nil || !strings.Contains(err.Error(), "instance key for "+host) || !strings.Contains(err.Error(), "not minted") {
144 t.Fatalf("a %s row: err = %v, want the key's refusal naming the host", layout, err)
145 }
146 if n := strings.Count(err.Error(), host+": "); n != 1 {
147 t.Fatalf("a %s row: the refusal names the host %d times: %v", layout, n, err)
148 }
149 if authorized != 1 {
150 t.Fatalf("authorized %d times, want the one guarded write the refusal came from", authorized)
151 }
152 if raw, _ := os.ReadFile(own); string(raw) != tok {
153 t.Fatalf("a refused %s row's env file was changed", layout)
154 }
155 })
156 }
157}
158