fix(uptime): allow RFC 1918 IPs for admin-configured monitors
HTTP/HTTPS uptime monitors targeting LAN addresses (192.168.x.x, 10.x.x.x, 172.16.x.x) permanently reported 'down' on fresh installs because SSRF protection rejects RFC 1918 ranges at two independent checkpoints: the URL validator (DNS-resolution layer) and the safe dialer (TCP-connect layer). Fixing only one layer leaves the monitor broken in practice. - Add IsRFC1918() predicate to the network package covering only the three RFC 1918 CIDRs; 169.254.x.x (link-local / cloud metadata) and loopback are intentionally excluded - Add WithAllowRFC1918() functional option to both SafeHTTPClient and ValidationConfig; option defaults to false so existing behaviour is unchanged for every call site except uptime monitors - In uptime_service.go, pass WithAllowRFC1918() to both ValidateExternalURL and NewSafeHTTPClient together; a coordinating comment documents that both layers must be relaxed as a unit - 169.254.169.254 and the full 169.254.0.0/16 link-local range remain unconditionally blocked; the cloud-metadata error path is preserved - 21 new tests across three packages, including an explicit regression guard that confirms RFC 1918 blocks are still applied without the option set (TestValidateExternalURL_RFC1918BlockedByDefault) Fixes issues 6 and 7 from the fresh-install bug report.
This commit is contained in:
@@ -120,6 +120,14 @@ type ValidationConfig struct {
|
||||
MaxRedirects int
|
||||
Timeout time.Duration
|
||||
BlockPrivateIPs bool
|
||||
|
||||
// AllowRFC1918 permits addresses in the RFC 1918 private ranges
|
||||
// (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16).
|
||||
//
|
||||
// SECURITY NOTE: Must only be set for admin-configured features such as uptime
|
||||
// monitors. Link-local (169.254.x.x), loopback, cloud metadata, and all other
|
||||
// restricted ranges remain blocked regardless of this flag.
|
||||
AllowRFC1918 bool
|
||||
}
|
||||
|
||||
// ValidationOption allows customizing validation behavior.
|
||||
@@ -145,6 +153,15 @@ func WithMaxRedirects(maxRedirects int) ValidationOption {
|
||||
return func(c *ValidationConfig) { c.MaxRedirects = maxRedirects }
|
||||
}
|
||||
|
||||
// WithAllowRFC1918 permits addresses in the RFC 1918 private ranges
|
||||
// (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16).
|
||||
//
|
||||
// Use only for admin-configured features (e.g., uptime monitors targeting internal hosts).
|
||||
// All other SSRF protections remain active.
|
||||
func WithAllowRFC1918() ValidationOption {
|
||||
return func(c *ValidationConfig) { c.AllowRFC1918 = true }
|
||||
}
|
||||
|
||||
// ValidateExternalURL validates a URL for external HTTP requests with comprehensive SSRF protection.
|
||||
// This function provides defense-in-depth against Server-Side Request Forgery attacks by:
|
||||
// 1. Validating URL format and scheme
|
||||
@@ -272,11 +289,23 @@ func ValidateExternalURL(rawURL string, options ...ValidationOption) (string, er
|
||||
if ip.To4() != nil && ip.To16() != nil && isIPv4MappedIPv6(ip) {
|
||||
// Extract the IPv4 address from the mapped format
|
||||
ipv4 := ip.To4()
|
||||
// Allow RFC 1918 IPv4-mapped IPv6 only when the caller has explicitly opted in.
|
||||
if config.AllowRFC1918 && network.IsRFC1918(ipv4) {
|
||||
continue
|
||||
}
|
||||
if network.IsPrivateIP(ipv4) {
|
||||
return "", fmt.Errorf("connection to private ip addresses is blocked for security (detected IPv4-mapped IPv6: %s)", ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Allow RFC 1918 addresses only when the caller has explicitly opted in
|
||||
// (e.g., admin-configured uptime monitors targeting internal hosts).
|
||||
// Link-local (169.254.x.x), loopback, cloud metadata, and all other
|
||||
// restricted ranges remain blocked regardless of this flag.
|
||||
if config.AllowRFC1918 && network.IsRFC1918(ip) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if IP is in private/reserved ranges using centralized network.IsPrivateIP
|
||||
// This includes:
|
||||
// - RFC 1918 private networks (10.x, 172.16.x, 192.168.x)
|
||||
|
||||
Reference in New Issue
Block a user