repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

3.3 KB · 109 lines · Go Raw History
  1package tools
  2
  3import (
  4	"context"
  5	"net"
  6	"net/http"
  7	"net/http/httptest"
  8	"strings"
  9	"testing"
 10	"time"
 11)
 12
 13func TestPublicURLRefusesEverythingButHttp(t *testing.T) {
 14	for _, c := range []struct{ in, want string }{
 15		{"file:///home/ubuntu/Downloads/070726 WellsFargo.pdf", "already in this conversation"},
 16		{"file:///etc/passwd", "no filesystem"},
 17		{"ftp://example.com/x", "only http and https"},
 18		{"gopher://example.com", "only http and https"},
 19		{"example.com/page", "needs to start with https"},
 20		{"http://orchard-auth:8000/verify", "not a public address"},
 21		{"http://localhost:8000/", "not a public address"},
 22		{"http://orchard-llm:8000/v1/models", "orchard tools"},
 23	} {
 24		got, err := publicURL(c.in)
 25		if err == nil {
 26			t.Errorf("%q was allowed as %q", c.in, got)
 27			continue
 28		}
 29		if !strings.Contains(err.Error(), c.want) {
 30			t.Errorf("%q gave %q, want it to mention %q", c.in, err, c.want)
 31		}
 32	}
 33}
 34
 35func TestPublicURLAllowsOrdinaryPages(t *testing.T) {
 36	for _, ok := range []string{
 37		"https://en.wikipedia.org/wiki/Asmongold",
 38		"http://example.com/a/b?c=d",
 39		"https://www.espn.com/soccer/team/fixtures/_/id/364/liverpool",
 40	} {
 41		if _, err := publicURL(ok); err != nil {
 42			t.Errorf("%q was refused: %v", ok, err)
 43		}
 44	}
 45}
 46
 47// The address ranges that reach this machine or its network. IsPrivate does not
 48// cover carrier grade NAT or the metadata address, and both matter.
 49func TestIsPublicIP(t *testing.T) {
 50	for _, bad := range []string{
 51		"127.0.0.1", "::1", "10.0.0.5", "172.18.0.3", "192.168.1.10",
 52		"169.254.169.254", "100.64.0.1", "0.0.0.0", "fe80::1", "fd00::1",
 53	} {
 54		if isPublicIP(net.ParseIP(bad)) {
 55			t.Errorf("%s was treated as public", bad)
 56		}
 57	}
 58	for _, good := range []string{"1.1.1.1", "93.184.216.34", "2606:4700:4700::1111"} {
 59		if !isPublicIP(net.ParseIP(good)) {
 60			t.Errorf("%s was treated as internal", good)
 61		}
 62	}
 63}
 64
 65// The string check can be got past by a name that resolves inward, so the real
 66// fence is on the dial. This proves it by pointing a real client at a real
 67// loopback server, which is exactly the shape of the attack.
 68func TestTheClientRefusesToDialLoopback(t *testing.T) {
 69	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 70		_, _ = w.Write([]byte("this must never be read"))
 71	}))
 72	defer srv.Close()
 73
 74	c := publicClient(5 * time.Second)
 75	req, err := http.NewRequestWithContext(context.Background(), "GET", srv.URL, nil)
 76	if err != nil {
 77		t.Fatal(err)
 78	}
 79	resp, err := c.Do(req)
 80	if err == nil {
 81		resp.Body.Close()
 82		t.Fatal("a loopback address was fetched")
 83	}
 84	if !strings.Contains(err.Error(), "refusing") {
 85		t.Errorf("err = %v, want the fence to have refused it", err)
 86	}
 87}
 88
 89// get() must not put a host in the penalty box for being internal, or one bad
 90// suggestion from the model locks out a host that never even answered.
 91func TestTheFenceDoesNotTripTheBreaker(t *testing.T) {
 92	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 93	defer srv.Close()
 94
 95	d := &Deps{
 96		HTTP:   srv.Client(),
 97		Public: publicClient(5 * time.Second),
 98		Now:    time.Now,
 99		Guard:  NewGuard(time.Minute),
100	}
101	if _, err := get(context.Background(), d, srv.URL, ""); err == nil {
102		t.Fatal("loopback was fetched through get")
103	}
104	host := hostOf(srv.URL)
105	if blocked, _ := d.Guard.Blocked(host); blocked {
106		t.Error("the breaker tripped on a refusal that was ours, not the host's")
107	}
108}