rastrillo / aviso Public

Clone
git clone https://amadan.net/rastrillo/aviso

Plain git — no account needed to clone.

Download

Download this file

1package aviso
2
3import (
4 "context"
5 "errors"
6 "net"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12)
13
14func TestValidateEndpoint(t *testing.T) {
15 ok := "https://fcm.googleapis.com/fcm/send/abc"
16 if err := validateEndpoint(ok); err != nil {
17 t.Fatalf("good endpoint refused: %v", err)
18 }
19 bad := map[string]string{
20 "http": "http://fcm.googleapis.com/x",
21 "userinfo": "https://user:pw@fcm.googleapis.com/x",
22 "fragment": "https://fcm.googleapis.com/x#frag",
23 "empty": "",
24 "no host": "https:///x",
25 "too long": "https://fcm.googleapis.com/" + strings.Repeat("a", 2048),
26 "loopback": "https://127.0.0.1/x",
27 "ip6 loop": "https://[::1]/x",
28 "private": "https://10.0.0.5/x",
29 "linklocal": "https://169.254.169.254/latest",
30 "mapped": "https://[::ffff:10.0.0.5]/x",
31 "testnet": "https://192.0.2.1/x",
32 "bench": "https://198.18.0.1/x",
33 }
34 for name, in := range bad {
35 if err := validateEndpoint(in); !errors.Is(err, ErrBadEndpoint) {
36 t.Errorf("%s (%q): got %v, want ErrBadEndpoint", name, in, err)
37 }
38 }
39}
40
41func TestGuardedIP(t *testing.T) {
42 refused := []string{
43 "127.0.0.1", "10.1.2.3", "172.16.0.1", "192.168.1.1", "169.254.1.1",
44 "::1", "fe80::1", "fc00::1", "::ffff:192.168.1.1", "0.0.0.0", "100.64.0.1",
45 // Reserved ranges the net.IP predicates do not cover.
46 "0.1.2.3", "192.0.0.1", "192.0.2.1", "198.18.0.1", "198.19.255.255", "198.51.100.1",
47 "203.0.113.1", "240.0.0.1", "255.255.255.255", "2001:db8::1", "64:ff9b::a00:1",
48 "64:ff9b:1::a00:1", "100::1", "2001:2::1", "3fff::1",
49 "::", "224.0.0.1", "ff02::1",
50 }
51 for _, ip := range refused {
52 if err := guardedIP(net.ParseIP(ip)); err == nil {
53 t.Errorf("%s allowed", ip)
54 }
55 }
56 for _, ip := range []string{"142.250.72.14", "2607:f8b0::1", "1.1.1.1"} {
57 if err := guardedIP(net.ParseIP(ip)); err != nil {
58 t.Errorf("%s refused: %v", ip, err)
59 }
60 }
61}
62
63// The guard is at connect time, so a hostname that resolves to a
64// loopback address — DNS rebinding's shape — fails even though the URL
65// looked fine. httptest's server IS loopback, which makes it the
66// perfect hostile target.
67func TestClientRefusesLoopbackAtDial(t *testing.T) {
68 srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
69 defer srv.Close()
70 c := newClient()
71 c.Transport.(*http.Transport).TLSClientConfig = srv.Client().Transport.(*http.Transport).TLSClientConfig.Clone()
72 req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, strings.Replace(srv.URL, "127.0.0.1", "localhost", 1), nil)
73 _, err := c.Do(req)
74 if err == nil || !strings.Contains(err.Error(), "aviso") {
75 t.Fatalf("loopback dial allowed or wrong error: %v", err)
76 }
77}
78
79// The transport's own dialer must refuse before any packet leaves: the
80// guard runs in Dialer.Control, which precedes connect(2), so each of
81// these fails instantly with the guard's error rather than a timeout.
82// Ports 9 (discard) would otherwise sit there.
83func TestClientDialerRefusesPrivateAddressesBeforeConnecting(t *testing.T) {
84 tr := newClient().Transport.(*http.Transport)
85 for _, addr := range []string{
86 "10.0.0.1:443", "172.16.5.5:443", "192.168.1.1:443", "169.254.169.254:80",
87 "[::ffff:10.0.0.1]:443", "[64:ff9b::a00:1]:443", "[64:ff9b:1::a00:1]:443",
88 "[fc00::1]:443", "[2001:db8::1]:443", "192.0.2.1:443", "127.0.0.1:9",
89 } {
90 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
91 conn, err := tr.DialContext(ctx, "tcp", addr)
92 cancel()
93 if conn != nil {
94 conn.Close()
95 }
96 if err == nil || !strings.Contains(err.Error(), "aviso: dial refused") {
97 t.Errorf("%s: got %v, want the guard's refusal", addr, err)
98 }
99 }
100}
101
102func TestClientRefusesRedirects(t *testing.T) {
103 c := newClient()
104 req, _ := http.NewRequest(http.MethodGet, "https://example.invalid/", nil)
105 if err := c.CheckRedirect(req, []*http.Request{req}); err == nil {
106 t.Fatal("redirect followed")
107 }
108 if c.Transport.(*http.Transport).Proxy != nil {
109 t.Fatal("client would honour an environment proxy")
110 }
111}
112