forked from
tangled.org/core
Monorepo for Tangled
6.0 kB
177 lines
1package microvm
2
3import (
4 "io"
5 "log/slog"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 "time"
11)
12
13func TestCacheProxyFallsBackOnNotFound(t *testing.T) {
14 first := httptest.NewServer(http.NotFoundHandler())
15 defer first.Close()
16 second := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
17 if req.URL.Path != "/abc.narinfo" {
18 t.Fatalf("path: got %q, want /abc.narinfo", req.URL.Path)
19 }
20 _, _ = io.WriteString(w, "ok")
21 }))
22 defer second.Close()
23
24 upstreams, err := parseCacheUpstreams([]string{first.URL, second.URL})
25 if err != nil {
26 t.Fatal(err)
27 }
28
29 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
30 rec := httptest.NewRecorder()
31 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
32
33 if rec.Code != http.StatusOK {
34 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
35 }
36 if got := rec.Body.String(); got != "ok" {
37 t.Fatalf("body: got %q, want ok", got)
38 }
39}
40
41func TestCacheProxyServesNixCacheInfoItself(t *testing.T) {
42 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
43 t.Errorf("upstream should not be hit, got request for %q", req.URL.Path)
44 }))
45 defer upstream.Close()
46
47 upstreams, err := parseCacheUpstreams([]string{upstream.URL})
48 if err != nil {
49 t.Fatal(err)
50 }
51
52 req := httptest.NewRequest(http.MethodGet, "http://guest/nix-cache-info", nil)
53 rec := httptest.NewRecorder()
54 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
55
56 if rec.Code != http.StatusOK {
57 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
58 }
59 if got := rec.Body.String(); got != nixCacheInfo {
60 t.Fatalf("body: got %q, want %q", got, nixCacheInfo)
61 }
62}
63
64func TestCacheProxyErrorStatusDoesNotWinRace(t *testing.T) {
65 erroring := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
66 http.Error(w, "misdirected", http.StatusMisdirectedRequest)
67 }))
68 defer erroring.Close()
69 healthy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
70 time.Sleep(50 * time.Millisecond) // lose the race to the erroring upstream
71 _, _ = io.WriteString(w, "ok")
72 }))
73 defer healthy.Close()
74
75 upstreams, err := parseCacheUpstreams([]string{erroring.URL, healthy.URL})
76 if err != nil {
77 t.Fatal(err)
78 }
79
80 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
81 rec := httptest.NewRecorder()
82 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
83
84 if rec.Code != http.StatusOK {
85 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
86 }
87 if got := rec.Body.String(); got != "ok" {
88 t.Fatalf("body: got %q, want ok", got)
89 }
90}
91
92func TestCacheProxyJoinsSubpathQueryAndAuth(t *testing.T) {
93 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
94 if req.URL.Path != "/sub/cache/abc.narinfo" {
95 t.Errorf("path: got %q, want /sub/cache/abc.narinfo", req.URL.Path)
96 }
97 if got := req.URL.Query().Get("token"); got != "s3cret" {
98 t.Errorf("token: got %q, want s3cret", got)
99 }
100 if user, pass, ok := req.BasicAuth(); !ok || user != "dawn" || pass != "woof" {
101 t.Errorf("basic auth: got %q/%q/%v, want dawn/woof/true", user, pass, ok)
102 }
103 _, _ = io.WriteString(w, "ok")
104 }))
105 defer upstream.Close()
106
107 upstreamURL := "http://dawn:woof@" + strings.TrimPrefix(upstream.URL, "http://") + "/sub/cache/?token=s3cret"
108 upstreams, err := parseCacheUpstreams([]string{upstreamURL})
109 if err != nil {
110 t.Fatal(err)
111 }
112
113 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
114 rec := httptest.NewRecorder()
115 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
116
117 if rec.Code != http.StatusOK {
118 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
119 }
120 if got := rec.Body.String(); got != "ok" {
121 t.Fatalf("body: got %q, want ok", got)
122 }
123}
124
125func TestCacheProxyGuardAllowsPublicIPv4(t *testing.T) {
126 if err := refuseSpecialPurposeAddrs("tcp", "104.26.13.82:443", nil); err != nil {
127 t.Fatalf("public IPv4 address was blocked: %v", err)
128 }
129}
130
131func TestCacheProxyGuardedUpstreamCannotReachBlockedRanges(t *testing.T) {
132 // httptest listens on 127.0.0.1, which is in the blocked ranges; reaching
133 // it would mean a workflow-defined cache can hit the host's loopback
134 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
135 t.Errorf("guarded upstream on loopback should not be reachable, got request for %q", req.URL.Path)
136 }))
137 defer upstream.Close()
138
139 upstreams, err := parseCacheUpstreams([]string{upstream.URL})
140 if err != nil {
141 t.Fatal(err)
142 }
143
144 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
145 rec := httptest.NewRecorder()
146 cacheProxyHandler(mergeCacheUpstreams(nil, upstreams), slog.Default()).ServeHTTP(rec, req)
147
148 if rec.Code != http.StatusBadGateway {
149 t.Fatalf("status: got %d, want 502; body=%q", rec.Code, rec.Body.String())
150 }
151}
152
153func TestCacheProxyRewritesHostHeader(t *testing.T) {
154 var upstreamHost string
155 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
156 if req.Host != upstreamHost {
157 t.Errorf("host: got %q, want %q", req.Host, upstreamHost)
158 }
159 _, _ = io.WriteString(w, "ok")
160 }))
161 defer upstream.Close()
162 upstreamHost = strings.TrimPrefix(upstream.URL, "http://")
163
164 upstreams, err := parseCacheUpstreams([]string{upstream.URL})
165 if err != nil {
166 t.Fatal(err)
167 }
168
169 req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:10500/abc.narinfo", nil)
170 req.Host = "127.0.0.1:10500"
171 rec := httptest.NewRecorder()
172 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
173
174 if rec.Code != http.StatusOK {
175 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
176 }
177}