@@ -7,9 +7,13 @@ import (
77 "context"
88 "encoding/json"
99 "errors"
10+ "fmt"
1011 "log/slog"
12+ "net/http"
13+ "net/http/httptest"
1114 neturl "net/url"
1215 "sync"
16+ "sync/atomic"
1317 "testing"
1418 "time"
1519
@@ -28,22 +32,21 @@ func TestMain(m *testing.M) {
2832}
2933
3034func TestAlwaysRevalidate (t * testing.T ) {
31- const proxy = "http://collateral-proxy.default.svc"
3235 for _ , tc := range []struct {
3336 name string
3437 url string
3538 want bool
3639 }{
3740 // CRLs and TCB / QE-identity must always revalidate.
3841 {"snp crl vendor" , "https://kdsintf.amd.com/vcek/v1/Milan/crl" , true },
39- {"snp crl proxy" , proxy + "/vcek/v1/Milan/crl" , true },
42+ {"snp crl proxy" , proxyBase + "/vcek/v1/Milan/crl" , true },
4043 {"tdx pckcrl vendor" , "https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=platform&encoding=der" , true },
41- {"tdx pckcrl proxy" , proxy + "/sgx/certification/v4/pckcrl?ca=platform&encoding=der" , true },
42- {"tdx tcb proxy" , proxy + "/tdx/certification/v4/tcb?fmspc=abc" , true },
44+ {"tdx pckcrl proxy" , proxyBase + "/sgx/certification/v4/pckcrl?ca=platform&encoding=der" , true },
45+ {"tdx tcb proxy" , proxyBase + "/tdx/certification/v4/tcb?fmspc=abc" , true },
4346 {"tdx root crl" , "https://certificates.trustedservices.intel.com/IntelSGXRootCA.der" , true },
4447 // VCEK certificates are immutable and served cache-first.
4548 {"vcek cert vendor" , "https://kdsintf.amd.com/vcek/v1/Milan/abc123" , false },
46- {"vcek cert proxy" , proxy + "/vcek/v1/Milan/abc123" , false },
49+ {"vcek cert proxy" , proxyBase + "/vcek/v1/Milan/abc123" , false },
4750 } {
4851 t .Run (tc .name , func (t * testing.T ) {
4952 assert .Equal (t , tc .want , alwaysRevalidate (tc .url ))
@@ -52,16 +55,15 @@ func TestAlwaysRevalidate(t *testing.T) {
5255}
5356
5457func TestRedirectToProxy (t * testing.T ) {
55- const proxy = "http://collateral-proxy.default.svc"
56- c := & CachedHTTPSGetter {collateralProxyBase : proxy }
58+ c := & CachedHTTPSGetter {collateralProxyBase : proxyBase }
5759 assert .Equal (t ,
58- proxy + "/IntelSGXRootCA.der" ,
60+ proxyBase + "/IntelSGXRootCA.der" ,
5961 c .redirectToProxy ("https://certificates.trustedservices.intel.com/IntelSGXRootCA.der" ))
6062 assert .Equal (t ,
61- proxy + "/vcek/v1/Milan/abc" ,
63+ proxyBase + "/vcek/v1/Milan/abc" ,
6264 c .redirectToProxy ("https://kdsintf.amd.com/vcek/v1/Milan/abc" ))
6365 assert .Equal (t ,
64- proxy + "/sgx/certification/v4/pckcrl?ca=platform&encoding=der" ,
66+ proxyBase + "/sgx/certification/v4/pckcrl?ca=platform&encoding=der" ,
6567 c .redirectToProxy ("https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=platform&encoding=der" ))
6668
6769 // With no proxy configured, nothing is rewritten.
@@ -249,7 +251,6 @@ func TestContextCancellation(t *testing.T) {
249251
250252func TestProxyFallback (t * testing.T ) {
251253 const (
252- proxyBase = "http://collateral-proxy.default.svc"
253254 proxyHost = "collateral-proxy.default.svc"
254255 kdsHost = "kdsintf.amd.com"
255256 )
@@ -263,7 +264,7 @@ func TestProxyFallback(t *testing.T) {
263264 errHosts : map [string ]error {proxyHost : errors .New ("dial tcp: connection refused" )},
264265 body : []byte ("crl-bytes" ),
265266 }
266- client , _ := newHostGetterClient (getter , proxyBase )
267+ client , _ := newHostGetterClient (getter )
267268
268269 _ , body , err := client .Get (directCRL )
269270 assert .NoError (err )
@@ -285,7 +286,7 @@ func TestProxyFallback(t *testing.T) {
285286 errHosts : map [string ]error {proxyHost : errors .New ("dial tcp: connection refused" )},
286287 body : []byte ("crl-bytes" ),
287288 }
288- client , testClock := newHostGetterClient (getter , proxyBase )
289+ client , testClock := newHostGetterClient (getter )
289290
290291 _ , _ , err := client .Get (directCRL )
291292 assert .NoError (err )
@@ -312,25 +313,93 @@ func TestProxyFallback(t *testing.T) {
312313 hits : map [string ]int {},
313314 errHosts : map [string ]error {proxyHost : & httpError {code : 404 , status : "404 Not Found" }},
314315 }
315- client , _ := newHostGetterClient (getter , proxyBase )
316+ client , _ := newHostGetterClient (getter )
316317
317318 _ , _ , err := client .Get (directCRL )
318319 assert .Error (err )
319320 assert .Equal (1 , getter .hits [proxyHost ])
320321 assert .Equal (0 , getter .hits [kdsHost ]) // upstream not contacted
321322 assert .False (client .proxyInCooldown ())
322323 })
324+
325+ for _ , code := range []int {http .StatusInternalServerError , http .StatusBadGateway , http .StatusTooManyRequests } {
326+ t .Run (fmt .Sprintf ("proxy answering %d falls back to upstream" , code ), func (t * testing.T ) {
327+ assert := assert .New (t )
328+ getter := & fakeHostGetter {
329+ hits : map [string ]int {},
330+ errHosts : map [string ]error {proxyHost : & httpError {code : code , status : http .StatusText (code )}},
331+ body : []byte ("crl-bytes" ),
332+ }
333+ client , _ := newHostGetterClient (getter )
334+
335+ _ , body , err := client .Get (directCRL )
336+ assert .NoError (err )
337+ assert .Equal ([]byte ("crl-bytes" ), body )
338+ assert .Equal (1 , getter .hits [proxyHost ])
339+ assert .Equal (1 , getter .hits [kdsHost ])
340+ assert .True (client .proxyInCooldown ())
341+ })
342+ }
343+
344+ t .Run ("5XX survives the retrier's error wrapping" , func (t * testing.T ) {
345+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
346+ w .WriteHeader (http .StatusInternalServerError )
347+ }))
348+ defer srv .Close ()
349+
350+ g := NewRetryHTTPSGetter (srv .Client (), 50 * time .Millisecond , slog .New (slog .DiscardHandler ))
351+ ctx , cancel := context .WithTimeout (t .Context (), 250 * time .Millisecond )
352+ defer cancel ()
353+ _ , _ , err := g .GetContext (ctx , srv .URL )
354+
355+ var httpErr * httpError
356+ require .ErrorAs (t , err , & httpErr )
357+ assert .True (t , transientStatus (httpErr .code ))
358+ })
359+
360+ t .Run ("retrying the proxy leaves budget for the fallback" , func (t * testing.T ) {
361+ proxySrv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
362+ w .WriteHeader (http .StatusInternalServerError )
363+ }))
364+ defer proxySrv .Close ()
365+ var upstreamHits atomic.Int32
366+ upstreamSrv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
367+ upstreamHits .Add (1 )
368+ _ , _ = w .Write ([]byte ("crl-bytes" ))
369+ }))
370+ defer upstreamSrv .Close ()
371+
372+ client := & CachedHTTPSGetter {
373+ ContextHTTPSGetter : NewRetryHTTPSGetter (proxySrv .Client (), 50 * time .Millisecond , slog .New (slog .DiscardHandler )),
374+ gcTicker : NeverGCTicker ,
375+ clock : clock.RealClock {},
376+ cache : memstore .New [string , []byte ](),
377+ logger : slog .New (slog .DiscardHandler ),
378+ collateralProxyBase : proxySrv .URL ,
379+ }
380+
381+ // The caller's budget is shorter than what retrying the proxy would like to spend.
382+ ctx , cancel := context .WithTimeout (t .Context (), 300 * time .Millisecond )
383+ defer cancel ()
384+ _ , body , err := client .GetContext (ctx , upstreamSrv .URL + "/vcek/v1/Milan/crl" )
385+ require .NoError (t , err )
386+ assert .Equal (t , []byte ("crl-bytes" ), body )
387+ assert .Equal (t , int32 (1 ), upstreamHits .Load ())
388+ assert .True (t , client .proxyInCooldown ())
389+ })
323390}
324391
325- func newHostGetterClient (getter * fakeHostGetter , collateralProxyBase string ) (* CachedHTTPSGetter , * testingclock.FakeClock ) {
392+ const proxyBase = "http://collateral-proxy.default.svc"
393+
394+ func newHostGetterClient (getter * fakeHostGetter ) (* CachedHTTPSGetter , * testingclock.FakeClock ) {
326395 testClock := testingclock .NewFakeClock (time .Now ())
327396 return & CachedHTTPSGetter {
328397 ContextHTTPSGetter : getter ,
329398 gcTicker : NeverGCTicker ,
330399 clock : testClock ,
331400 cache : memstore .New [string , []byte ](),
332401 logger : slog .Default (),
333- collateralProxyBase : collateralProxyBase ,
402+ collateralProxyBase : proxyBase ,
334403 }, testClock
335404}
336405
0 commit comments