carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package edge
2
3import (
4 "context"
5 "crypto/ed25519"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "os"
10 "path/filepath"
11 "slices"
12 "strings"
13 "testing"
14
15 "github.com/carlosframework/platform/internal/registry"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19func siteDirOf(t *testing.T, store release.Store, app, version string) string {
20 t.Helper()
21 raw, _, err := store.Get(context.Background(), release.ManifestKey(release.OpsAccount, app, version))
22 if err != nil {
23 t.Fatal(err)
24 }
25 return siteDirName(version, release.ManifestDigest(raw))
26}
27
28func digestSite(t *testing.T, ratchet *release.DigestRatchet) (release.Store, *staticSite, *httptest.Server, func(string, map[string]string)) {
29 t.Helper()
30 store := release.NewDirStore(filepath.Join(t.TempDir(), "store"))
31 priv, pub := testKeys(t)
32 if err := release.WriteDigestMarker(context.Background(), store, release.RootSigner(priv), release.OpsAccount, "test"); err != nil {
33 t.Fatal(err)
34 }
35 promote := func(v string, files map[string]string) {
36 shipSite(t, store, "site", v, files)
37 if err := release.Promote(context.Background(), store, priv, release.OpsAccount, "site", "canary/x", v); err != nil {
38 t.Fatal(err)
39 }
40 }
41 promote("v1", map[string]string{"index.html": "home v1"})
42 rt := registry.Route{Host: "site.example.com", Kind: "static", App: "site", Channel: "canary/x", Status: "ready"}
43 h := newStaticHandler(rt, store, pub, filepath.Join(t.TempDir(), "cache")).(*staticSite)
44 h.recheck = 0
45 h.ratchet = ratchet
46 srv := httptest.NewServer(h)
47 t.Cleanup(srv.Close)
48 return store, h, srv, promote
49}
50
51func getBody(t *testing.T, url string) (string, string) {
52 t.Helper()
53 res, err := http.Get(url)
54 if err != nil {
55 t.Fatal(err)
56 }
57 defer res.Body.Close()
58 b, _ := io.ReadAll(res.Body)
59 return string(b), res.Header.Get("X-Carlos-Version")
60}
61
62// Spec §8: a static site whose version name is reused, under a pointer with
63// digests, is refused before extraction.
64func TestStaticRefusesAReusedNameBeforeExtraction(t *testing.T) {
65 store, h, srv, promote := digestSite(t, nil)
66 if b, v := getBody(t, srv.URL+"/"); b != "home v1" || v != "v1" {
67 t.Fatalf("first serve = %q %q", b, v)
68 }
69 saved, _, _ := store.Get(context.Background(), release.ChannelKey(release.OpsAccount, "site", "canary/x"))
70 promote("v2", map[string]string{"index.html": "home v2"})
71 if b, _ := getBody(t, srv.URL+"/"); b != "home v2" {
72 t.Fatalf("v2 not served: %q", b)
73 }
74 if err := release.DeleteVersion(context.Background(), store, release.OpsAccount, "site", "v1"); err != nil {
75 t.Fatal(err)
76 }
77 shipSite(t, store, "site", "v1", map[string]string{"index.html": "planted"})
78 _, etag, _ := store.Get(context.Background(), release.ChannelKey(release.OpsAccount, "site", "canary/x"))
79 if _, err := store.Put(context.Background(), release.ChannelKey(release.OpsAccount, "site", "canary/x"), saved, etag); err != nil {
80 t.Fatal(err)
81 }
82 if b, v := getBody(t, srv.URL+"/"); b == "planted" || v != "v2" {
83 t.Fatalf("after the replay = %q %q; want v2 still served", b, v)
84 }
85 if _, err := os.Stat(filepath.Join(h.appDir(), siteDirOf(t, store, "site", "v1"))); err == nil {
86 t.Fatal("the reused name's archive was extracted")
87 }
88}
89
90// Spec §8: a cached tree from other bytes is not served.
91func TestStaticNeverServesACachedTreeFromOtherBytes(t *testing.T) {
92 store, h, srv, _ := digestSite(t, nil)
93 other := filepath.Join(h.appDir(), siteDirName("v1", release.ManifestDigest([]byte("other manifest"))))
94 if err := os.MkdirAll(other, 0o755); err != nil {
95 t.Fatal(err)
96 }
97 if err := os.WriteFile(filepath.Join(other, "index.html"), []byte("stale tree"), 0o644); err != nil {
98 t.Fatal(err)
99 }
100 if b, _ := getBody(t, srv.URL+"/"); b != "home v1" {
101 t.Fatalf("served %q from a tree keyed by another digest", b)
102 }
103 if _, err := os.Stat(filepath.Join(h.appDir(), siteDirOf(t, store, "site", "v1"), "index.html")); err != nil {
104 t.Fatalf("not extracted under its own digest: %v", err)
105 }
106}
107
108// Review Focus 5.
109func TestStaticNeverReusesALegacyCacheDir(t *testing.T) {
110 _, h, srv, _ := digestSite(t, nil)
111 legacy := filepath.Join(h.appDir(), "v1")
112 if err := os.MkdirAll(legacy, 0o755); err != nil {
113 t.Fatal(err)
114 }
115 if err := os.WriteFile(filepath.Join(legacy, "index.html"), []byte("pre-upgrade tree"), 0o644); err != nil {
116 t.Fatal(err)
117 }
118 if b, _ := getBody(t, srv.URL+"/"); b != "home v1" {
119 t.Fatalf("served %q from the bare-version directory", b)
120 }
121}
122
123func TestStaticRatchetRefusesALegacyPointerAfterDigests(t *testing.T) {
124 r := release.NewDigestRatchet(t.TempDir(), nil, false)
125 _, _, srv, _ := digestSite(t, r)
126 if _, v := getBody(t, srv.URL+"/"); v != "v1" {
127 t.Fatalf("version %q", v)
128 }
129 if err := r.Admit(release.OpsAccount, &release.ChannelPointer{App: "site", Channel: "canary/x", Version: "v1"}); err == nil {
130 t.Fatal("the edge's ratchet did not arm on the digest-bearing pointer it served")
131 }
132}
133
134func TestStaticWithoutDigestsServesAsBefore(t *testing.T) {
135 _, _, _, srv := staticFixture(t)
136 if b, v := getBody(t, srv.URL+"/"); b != "home v1" || v != "v1" {
137 t.Fatalf("legacy site = %q %q", b, v)
138 }
139}
140
141// prodStaticEdge is an Edge whose static serving is configured the way the
142// edge process configures it, through enableStaticFromEnv: the store and key
143// from the environment, and the ratchet built there from dataDir and
144// CARLOS_OPT_DIR (Astra r1 #6). The pointer cache is off, so every request
145// re-reads the pointer.
146type prodStaticEdge struct {
147 store release.Store
148 priv ed25519.PrivateKey
149 data string
150 srv *httptest.Server
151}
152
153func newProdStaticEdge(t *testing.T, require bool, seed func(release.Store, ed25519.PrivateKey)) *prodStaticEdge {
154 t.Helper()
155 storeDir := filepath.Join(t.TempDir(), "store")
156 store := release.NewDirStore(storeDir)
157 seedB64, pubB64, err := release.GenerateKey()
158 if err != nil {
159 t.Fatal(err)
160 }
161 priv, err := release.ParseSigningKey(seedB64, "test")
162 if err != nil {
163 t.Fatal(err)
164 }
165 seed(store, priv)
166 t.Setenv("CARLOS_RELEASE_PUBKEY", pubB64)
167 t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
168 t.Setenv("CARLOS_DEPLOYMENT_DIR", storeDir)
169 t.Setenv("CARLOS_OPT_DIR", t.TempDir())
170 flag := ""
171 if require {
172 flag = "1"
173 }
174 t.Setenv(release.RequirePointerDigestEnv, flag)
175 reg := openReg(t)
176 if err := reg.Upsert(registry.Route{Host: "site.example.com", Kind: "static", App: "site", Channel: "canary/x", Status: "ready"}); err != nil {
177 t.Fatal(err)
178 }
179 e, err := New(reg, nil)
180 if err != nil {
181 t.Fatal(err)
182 }
183 data := t.TempDir()
184 if err := enableStaticFromEnv(e, data); err != nil {
185 t.Fatal(err)
186 }
187 e.SetStaticRecheck(0)
188 if err := e.Reload(); err != nil {
189 t.Fatal(err)
190 }
191 srv := httptest.NewServer(e)
192 t.Cleanup(srv.Close)
193 return &prodStaticEdge{store: store, priv: priv, data: data, srv: srv}
194}
195
196func (p *prodStaticEdge) get(t *testing.T) (int, string, string) {
197 t.Helper()
198 req, err := http.NewRequest("GET", p.srv.URL+"/", nil)
199 if err != nil {
200 t.Fatal(err)
201 }
202 req.Host = "site.example.com"
203 res, err := http.DefaultClient.Do(req)
204 if err != nil {
205 t.Fatal(err)
206 }
207 defer res.Body.Close()
208 b, _ := io.ReadAll(res.Body)
209 return res.StatusCode, string(b), res.Header.Get("X-Carlos-Version")
210}
211
212// extracted lists the site trees under the edge's static cache.
213func (p *prodStaticEdge) extracted(t *testing.T) []string {
214 t.Helper()
215 entries, err := os.ReadDir(filepath.Join(p.data, "static", release.OpsAccount, "site"))
216 if err != nil && !os.IsNotExist(err) {
217 t.Fatal(err)
218 }
219 var out []string
220 for _, e := range entries {
221 if e.IsDir() && !strings.HasPrefix(e.Name(), ".") {
222 out = append(out, e.Name())
223 }
224 }
225 return out
226}
227
228func (p *prodStaticEdge) promote(t *testing.T, v, page string) {
229 t.Helper()
230 shipSite(t, p.store, "site", v, map[string]string{"index.html": page})
231 if err := release.Promote(context.Background(), p.store, p.priv, release.OpsAccount, "site", "canary/x", v); err != nil {
232 t.Fatal(err)
233 }
234}
235
236// Astra r1 #6: the static handler, configured as production configures it,
237// refuses a replayed legacy pointer once it has served a pointer with
238// digests. The saved v1 pointer comes back with v1's name reshipped under
239// other bytes; the edge keeps serving v2 and extracts nothing new. The
240// control is the same legacy pointer served on first sight.
241func TestEdgeStaticRefusesAReplayedLegacyPointer(t *testing.T) {
242 p := newProdStaticEdge(t, false, func(release.Store, ed25519.PrivateKey) {})
243 ctx := context.Background()
244 p.promote(t, "v1", "home v1") // no marker: a legacy pointer
245 key := release.ChannelKey(release.OpsAccount, "site", "canary/x")
246 saved, _, err := p.store.Get(ctx, key)
247 if err != nil {
248 t.Fatal(err)
249 }
250 if code, b, v := p.get(t); code != http.StatusOK || b != "home v1" || v != "v1" {
251 t.Fatalf("control: the legacy pointer on first sight = %d %q %q", code, b, v)
252 }
253 if err := release.WriteDigestMarker(ctx, p.store, release.RootSigner(p.priv), release.OpsAccount, "test"); err != nil {
254 t.Fatal(err)
255 }
256 p.promote(t, "v2", "home v2") // with digests: the edge marks the channel
257 if code, b, v := p.get(t); code != http.StatusOK || b != "home v2" || v != "v2" {
258 t.Fatalf("the digest pointer = %d %q %q", code, b, v)
259 }
260 before := p.extracted(t)
261 if err := release.DeleteVersion(ctx, p.store, release.OpsAccount, "site", "v1"); err != nil {
262 t.Fatal(err)
263 }
264 shipSite(t, p.store, "site", "v1", map[string]string{"index.html": "planted"})
265 _, etag, _ := p.store.Get(ctx, key)
266 if _, err := p.store.Put(ctx, key, saved, etag); err != nil {
267 t.Fatal(err)
268 }
269 if code, b, v := p.get(t); code != http.StatusOK || b != "home v2" || v != "v2" {
270 t.Fatalf("after the replay = %d %q %q; want v2 still served", code, b, v)
271 }
272 if after := p.extracted(t); !slices.Equal(before, after) {
273 t.Fatalf("the replay extracted a tree: before %v, after %v", before, after)
274 }
275}
276
277// CARLOS_REQUIRE_POINTER_DIGEST through the edge's own ratchet construction:
278// a fresh box with the flag on refuses a legacy pointer for a channel it has
279// never seen, so nothing is extracted or served; with the flag off (the
280// control) the same pointer serves.
281func TestEdgeStaticRequireFlagOnAFreshBox(t *testing.T) {
282 for _, on := range []bool{false, true} {
283 p := newProdStaticEdge(t, on, func(s release.Store, priv ed25519.PrivateKey) {
284 shipSite(t, s, "site", "v1", map[string]string{"index.html": "home v1"})
285 if err := release.Promote(context.Background(), s, priv, release.OpsAccount, "site", "canary/x", "v1"); err != nil {
286 t.Fatal(err)
287 }
288 })
289 code, b, _ := p.get(t)
290 switch {
291 case on && (code == http.StatusOK || len(p.extracted(t)) != 0):
292 t.Fatalf("flag on: a fresh box served a legacy pointer: %d %q, extracted %v", code, b, p.extracted(t))
293 case !on && (code != http.StatusOK || b != "home v1"):
294 t.Fatalf("control, flag off: %d %q, want home v1", code, b)
295 }
296 }
297}
298