This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

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