This repository has no description
0

Configure Feed

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

core / appview / serververify / verify_test.go
2.8 kB 108 lines
1package serververify 2 3import ( 4 "context" 5 "fmt" 6 "net" 7 "net/http" 8 "net/http/httptest" 9 "sync/atomic" 10 "testing" 11 "time" 12) 13 14const ssrfExpectedOwner = "did:plc:ssrfguardexpectedowner" 15 16func TestRunVerificationRejectsNonPublicDestinationsInProd(t *testing.T) { 17 loopbackDomain, loopbackHits := localOwnerEndpoint(t, "127.0.0.1") 18 19 cases := []struct { 20 name string 21 domain string 22 hits *atomic.Int32 23 }{ 24 { 25 name: "loopback address with a real owner endpoint", 26 domain: loopbackDomain, 27 hits: loopbackHits, 28 }, 29 { 30 name: "private address", 31 domain: "10.0.0.1:80", 32 }, 33 { 34 name: "link-local metadata address", 35 domain: "169.254.169.254:80", 36 }, 37 { 38 name: "reserved unspecified address", 39 domain: "0.0.0.0:80", 40 }, 41 } 42 43 for _, tc := range cases { 44 t.Run(tc.name, func(t *testing.T) { 45 if tc.hits != nil { 46 tc.hits.Store(0) 47 } 48 49 ctx, cancel := context.WithTimeout(context.Background(), 750*time.Millisecond) 50 defer cancel() 51 52 started := time.Now() 53 err := RunVerification(ctx, tc.domain, ssrfExpectedOwner, false) 54 elapsed := time.Since(started) 55 56 if err == nil { 57 t.Fatalf("RunVerification(%q, dev=false) succeeded; non-public destinations must be refused", tc.domain) 58 } 59 if elapsed > 250*time.Millisecond { 60 t.Fatalf("RunVerification(%q, dev=false) took %s; want an immediate SSRF refusal, not network IO until timeout", tc.domain, elapsed) 61 } 62 if tc.hits != nil && tc.hits.Load() != 0 { 63 t.Fatalf("RunVerification(%q, dev=false) reached the owner endpoint %d time(s); guard must refuse before normal network IO", tc.domain, tc.hits.Load()) 64 } 65 }) 66 } 67} 68 69func TestRunVerificationAllowsNonPublicDestinationsInDev(t *testing.T) { 70 loopbackDomain, loopbackHits := localOwnerEndpoint(t, "127.0.0.1") 71 72 ctx, cancel := context.WithTimeout(context.Background(), 750*time.Millisecond) 73 defer cancel() 74 75 err := RunVerification(ctx, loopbackDomain, ssrfExpectedOwner, true) 76 if err != nil { 77 t.Fatalf("RunVerification(%q, dev=true) failed: %v", loopbackDomain, err) 78 } 79 80 if loopbackHits.Load() != 1 { 81 t.Fatalf("RunVerification(%q, dev=true) did not reach owner endpoint", loopbackDomain) 82 } 83} 84 85func localOwnerEndpoint(t *testing.T, host string) (string, *atomic.Int32) { 86 t.Helper() 87 88 ln, err := net.Listen("tcp", net.JoinHostPort(host, "0")) 89 if err != nil { 90 t.Fatalf("listen on %s: %v", host, err) 91 } 92 93 var hits atomic.Int32 94 server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 95 hits.Add(1) 96 if r.URL.Path != "/xrpc/sh.tangled.owner" { 97 http.NotFound(w, r) 98 return 99 } 100 w.Header().Set("Content-Type", "application/json") 101 fmt.Fprintf(w, `{"owner":%q}`, ssrfExpectedOwner) 102 })) 103 server.Listener = ln 104 server.Start() 105 t.Cleanup(server.Close) 106 107 return ln.Addr().String(), &hits 108}