Add the shared packages for the sharks stack
model, tunnel, wg, authtoken, netguard, docker and dotenv, split out of the original single repository so Hammerhead, Blacktip and Bamboo can live in their own repos and import them. README extended; LICENSE unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c7c2ae8f8d
commit
2259580adf
16 files changed
+1359
-1
No files matched your search
@@ -0,0 +1,107 @@
|
||||
// Package netguard decides which network destinations a server-side feature
|
||||
// may be pointed at. Anything that makes outbound connections on a user's behalf
|
||||
// (webhooks, mail delivery) must refuse internal addresses by default, or it can
|
||||
// be used to probe the machine's own network and cloud metadata services.
|
||||
package netguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var internal = func() []*net.IPNet {
|
||||
var out []*net.IPNet
|
||||
for _, c := range []string{"0.0.0.0/8", "100.64.0.0/10", "192.0.0.0/24", "198.18.0.0/15", "240.0.0.0/4", "64:ff9b::/96", "2001:db8::/32"} {
|
||||
_, n, _ := net.ParseCIDR(c)
|
||||
out = append(out, n)
|
||||
}
|
||||
return out
|
||||
}()
|
||||
|
||||
// Blocked reports whether ip is private, loopback, link-local, multicast,
|
||||
// unspecified, or in a range reserved for carrier-grade NAT (which includes
|
||||
// the WireGuard tunnel network) and similar.
|
||||
func Blocked(ip net.IP) bool {
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
ip = v4
|
||||
}
|
||||
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
for _, n := range internal {
|
||||
if n.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrBlocked is returned when every address a name resolves to is internal.
|
||||
var ErrBlocked = errors.New("not allowed: that address is on an internal network")
|
||||
|
||||
// DialContext resolves the host itself, checks every address, and connects to
|
||||
// the one it checked, so a name that changes between the check and the
|
||||
// connection (DNS rebinding) can't slip through.
|
||||
func DialContext(allowPrivate bool) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ips []net.IP
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
ips = []net.IP{ip}
|
||||
} else {
|
||||
res, err := net.DefaultResolver.LookupIPAddr(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range res {
|
||||
ips = append(ips, r.IP)
|
||||
}
|
||||
}
|
||||
var last error = errors.New("no addresses")
|
||||
blocked := false
|
||||
d := net.Dialer{Timeout: 10 * time.Second}
|
||||
for _, ip := range ips {
|
||||
if !allowPrivate && Blocked(ip) {
|
||||
blocked = true
|
||||
continue
|
||||
}
|
||||
c, err := d.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
last = err
|
||||
}
|
||||
if blocked && !allowPrivate {
|
||||
return nil, ErrBlocked
|
||||
}
|
||||
return nil, last
|
||||
}
|
||||
}
|
||||
|
||||
// Client is an HTTP client for user-supplied URLs: guarded dialing, a short
|
||||
// timeout, and no following of redirects (a redirect could point somewhere internal).
|
||||
func Client(allowPrivate bool) *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: 12 * time.Second,
|
||||
Transport: &http.Transport{DialContext: DialContext(allowPrivate), TLSHandshakeTimeout: 10 * time.Second, DisableKeepAlives: true},
|
||||
CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
|
||||
}
|
||||
}
|
||||
|
||||
// PortOf is a small helper for URLs without an explicit port.
|
||||
func PortOf(scheme, port string) string {
|
||||
if port != "" {
|
||||
return port
|
||||
}
|
||||
if scheme == "https" {
|
||||
return strconv.Itoa(443)
|
||||
}
|
||||
return strconv.Itoa(80)
|
||||
}
|
||||
Reference in new issue
Block a user