File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/webhook/deny.go
101 lines · 2.7kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
// Package webhook is one HTTP POST, which is why barerepo can refuse every integration. 23.
133728e barerepo 1mo
2
package webhook
133728e barerepo 1mo
3
133728e barerepo 1mo
4
import (
133728e barerepo 1mo
5
"context"
133728e barerepo 1mo
6
"fmt"
133728e barerepo 1mo
7
"net"
133728e barerepo 1mo
8
"net/url"
133728e barerepo 1mo
9
)
133728e barerepo 1mo
10
133728e barerepo 1mo
11
// denied are the ranges a user-controlled URL may never reach. Chapter 23.3, and classic SSRF.
133728e barerepo 1mo
12
var denied = mustParse(
133728e barerepo 1mo
13
"127.0.0.0/8", // loopback
133728e barerepo 1mo
14
"10.0.0.0/8", // private
133728e barerepo 1mo
15
"172.16.0.0/12", // private
133728e barerepo 1mo
16
"192.168.0.0/16", // private
133728e barerepo 1mo
17
"169.254.0.0/16", // link local, and the cloud metadata service
133728e barerepo 1mo
18
"100.64.0.0/10", // carrier grade nat
133728e barerepo 1mo
19
"::1/128", // loopback
133728e barerepo 1mo
20
"fc00::/7", // unique local
133728e barerepo 1mo
21
"fe80::/10", // link local
133728e barerepo 1mo
22
"0.0.0.0/8", // this network
133728e barerepo 1mo
23
"::/128", // unspecified
133728e barerepo 1mo
24
)
133728e barerepo 1mo
25
133728e barerepo 1mo
26
func mustParse(cidrs ...string) []*net.IPNet {
133728e barerepo 1mo
27
out := make([]*net.IPNet, 0, len(cidrs))
133728e barerepo 1mo
28
for _, c := range cidrs {
133728e barerepo 1mo
29
_, n, err := net.ParseCIDR(c)
133728e barerepo 1mo
30
if err != nil {
133728e barerepo 1mo
31
panic(err)
133728e barerepo 1mo
32
}
133728e barerepo 1mo
33
out = append(out, n)
133728e barerepo 1mo
34
}
133728e barerepo 1mo
35
return out
133728e barerepo 1mo
36
}
133728e barerepo 1mo
37
133728e barerepo 1mo
38
// Allowed checks every resolved address, not the string, since a public name can mean loopback.
133728e barerepo 1mo
39
func Allowed(ctx context.Context, raw string) error {
133728e barerepo 1mo
40
u, err := url.Parse(raw)
133728e barerepo 1mo
41
if err != nil {
133728e barerepo 1mo
42
return fmt.Errorf("that is not a url")
133728e barerepo 1mo
43
}
133728e barerepo 1mo
44
if u.Scheme != "https" {
133728e barerepo 1mo
45
// Plain http would send the signature and the body in clear.
133728e barerepo 1mo
46
return fmt.Errorf("a webhook url must be https")
133728e barerepo 1mo
47
}
133728e barerepo 1mo
48
host := u.Hostname()
133728e barerepo 1mo
49
if host == "" {
133728e barerepo 1mo
50
return fmt.Errorf("that url has no host")
133728e barerepo 1mo
51
}
133728e barerepo 1mo
52
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, host)
133728e barerepo 1mo
53
if err != nil {
133728e barerepo 1mo
54
return fmt.Errorf("%s does not resolve", host)
133728e barerepo 1mo
55
}
133728e barerepo 1mo
56
if len(addrs) == 0 {
133728e barerepo 1mo
57
return fmt.Errorf("%s resolves to nothing", host)
133728e barerepo 1mo
58
}
133728e barerepo 1mo
59
for _, a := range addrs {
133728e barerepo 1mo
60
if err := allowedIP(a.IP); err != nil {
133728e barerepo 1mo
61
return err
133728e barerepo 1mo
62
}
133728e barerepo 1mo
63
}
133728e barerepo 1mo
64
return nil
133728e barerepo 1mo
65
}
133728e barerepo 1mo
66
133728e barerepo 1mo
67
// allowedIP checks one address, and every address must pass, or a mixed answer is the way through.
133728e barerepo 1mo
68
func allowedIP(ip net.IP) error {
133728e barerepo 1mo
69
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||
133728e barerepo 1mo
70
ip.IsInterfaceLocalMulticast() || ip.IsUnspecified() || ip.IsMulticast() {
133728e barerepo 1mo
71
return fmt.Errorf("%s is not a public address", ip)
133728e barerepo 1mo
72
}
133728e barerepo 1mo
73
for _, n := range denied {
133728e barerepo 1mo
74
if n.Contains(ip) {
133728e barerepo 1mo
75
return fmt.Errorf("%s is in %s, which webhooks may not reach", ip, n)
133728e barerepo 1mo
76
}
133728e barerepo 1mo
77
}
133728e barerepo 1mo
78
return nil
133728e barerepo 1mo
79
}
133728e barerepo 1mo
80
133728e barerepo 1mo
81
// AllowedAddr runs at connect time too, closing the gap where a name changes what it resolves to.
133728e barerepo 1mo
82
func AllowedAddr(addr string) error {
133728e barerepo 1mo
83
host, _, err := net.SplitHostPort(addr)
133728e barerepo 1mo
84
if err != nil {
133728e barerepo 1mo
85
host = addr
133728e barerepo 1mo
86
}
133728e barerepo 1mo
87
ip := net.ParseIP(host)
133728e barerepo 1mo
88
if ip == nil {
133728e barerepo 1mo
89
return fmt.Errorf("%s is not an address", host)
133728e barerepo 1mo
90
}
133728e barerepo 1mo
91
return allowedIP(ip)
133728e barerepo 1mo
92
}
133728e barerepo 1mo
93
133728e barerepo 1mo
94
// DeniedRanges is what the deny list holds, for the config page to show.
133728e barerepo 1mo
95
func DeniedRanges() []string {
133728e barerepo 1mo
96
out := make([]string, 0, len(denied))
133728e barerepo 1mo
97
for _, n := range denied {
133728e barerepo 1mo
98
out = append(out, n.String())
133728e barerepo 1mo
99
}
133728e barerepo 1mo
100
return out
133728e barerepo 1mo
101
}
history · rawbarerepo 0.1.0