Monorepo for Tangled
0

Configure Feed

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

tangled-core / spindle / engines / microvm / read_cache_proxy_test.go
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}