package edge import ( "net/http" "net/http/httptest" "strings" "testing" ) // The "not here YET" answer (#174). // pendingEdge is an edge serving one route, with the pending answer armed // for go.tito.example and apps.example. func pendingEdge(t *testing.T, fallback string) *Edge { t.Helper() e := purgeHostsEdge(t, titoAccount(), func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) e.fallbackURL = fallback e.setPlatformDomains(platformDomainsFromEnv(func(k string) string { return map[string]string{ "CARLOS_WILDCARD_DOMAINS": "go.tito.example, GO.TITO.EXAMPLE", "PLATFORM_APPS_DOMAIN": "apps.example.", }[k] })) return e } func get(e *Edge, method, url string) *httptest.ResponseRecorder { rec := httptest.NewRecorder() e.ServeHTTP(rec, httptest.NewRequest(method, url, nil)) return rec } // TestPendingHostUnderAPlatformDomainIs503 is the headline: a host the edge // has not heard of yet, under a platform domain, gets a retryable answer // rather than a dead end. func TestPendingHostUnderAPlatformDomainIs503(t *testing.T) { e := pendingEdge(t, "") for _, url := range []string{ "https://sb-andyet.go.tito.example/dashboard", "https://acct.apps.example/", "https://app.acct.apps.example/x?y=1", } { rec := get(e, "GET", url) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("%s: status %d, want 503", url, rec.Code) } if got := rec.Header().Get("Retry-After"); got != pendingRetryAfter { t.Fatalf("%s: Retry-After %q, want %q", url, got, pendingRetryAfter) } if got := rec.Header().Get("Cache-Control"); got != "no-store" { t.Fatalf("%s: Cache-Control %q, want no-store", url, got) } body := rec.Body.String() if !strings.Contains(body, pendingHeading) || !strings.Contains(body, "location.replace(location.href)") { t.Fatalf("%s: body is not the retrying pending page:\n%s", url, body) } } } // TestPendingPageNeverEchoesTheRequest — the host, path and query are // attacker-chosen for an unknown host; none of them may reach the page. func TestPendingPageNeverEchoesTheRequest(t *testing.T) { e := pendingEdge(t, "") rec := get(e, "GET", "https://xss-probe.go.tito.example/%3Cscript%3E?q=%3Cimg%3E") body := rec.Body.String() for _, s := range []string{"xss-probe", "`) body := get(e, "GET", "https://typo.go.tito.example/").Body.String() if !strings.Contains(body, "location.replace(f)") { t.Fatalf("the page does not go to the fallback when it gives up:\n%s", body) } if strings.Count(body, "") != 1 { t.Fatalf("the fallback URL broke out of the script element:\n%s", body) } if !strings.Contains(body, `"https://www.example.com/?a=1`+"\\u0026"+`b=\"x\"`+"\\u003c"+`/script`+"\\u003e"+`"`) { t.Fatalf("the fallback URL is not embedded as an escaped JS string:\n%s", body) } } // TestPendingAnswerIsOffWithoutPlatformDomains — New alone (no env) arms // nothing: every unknown host is the plain 404, as before #174. func TestPendingAnswerIsOffWithoutPlatformDomains(t *testing.T) { e := purgeHostsEdge(t, titoAccount(), func(w http.ResponseWriter, r *http.Request) {}) if rec := get(e, "GET", "https://sb-new.go.tito.example/"); rec.Code != http.StatusNotFound { t.Fatalf("status %d, want 404 with no platform domains configured", rec.Code) } } // TestKnownHostUnderAPlatformDomainIsServed — the pending answer is only // ever the miss branch. func TestKnownHostUnderAPlatformDomainIsServed(t *testing.T) { e := pendingEdge(t, "") if rec := get(e, "GET", "https://acme.go.tito.example/"); rec.Code != http.StatusOK { t.Fatalf("a served host under a platform domain got %d", rec.Code) } } // TestPendingHostOnPlainHTTP — :80 for an unknown platform-domain host gets // the same answer rather than a redirect into a handshake. func TestPendingHostOnPlainHTTP(t *testing.T) { e := pendingEdge(t, "") rec := httptest.NewRecorder() e.httpFallbackHandler().ServeHTTP(rec, httptest.NewRequest("GET", "http://sb-new.go.tito.example/", nil)) if rec.Code != http.StatusServiceUnavailable { t.Fatalf(":80 status %d, want 503", rec.Code) } } // TestPendingAnswerNeedsTheRouteTableForm — routing matches r.Host byte for // byte, so an uppercase or trailing-dot spelling of a platform host can miss // even when the host is served. Those keep the plain unknown-host answer // rather than 90 seconds of "still being set up". func TestPendingAnswerNeedsTheRouteTableForm(t *testing.T) { e := pendingEdge(t, "") for _, host := range []string{"Acme.go.tito.example", "acme.go.tito.example.", "SB-NEW.GO.TITO.EXAMPLE"} { req := httptest.NewRequest("GET", "https://x/", nil) req.Host = host rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusNotFound { t.Fatalf("%s: status %d, want the plain 404", host, rec.Code) } } }