Module: Legate::Auth::UrlGuard

Defined in:
lib/legate/auth/url_guard.rb

Overview

Canonical SSRF guard for outbound auth and credential-test URLs.

Resolves the host and refuses loopback, link-local, private, 0.0.0.0/8 and CGNAT (100.64.0.0/10) targets so a misconfigured or attacker-supplied URL cannot reach internal services or cloud metadata. Set LEGATE_ALLOW_PRIVATE_AUTH_URLS=1 to bypass in development.

Constant Summary collapse

BLOCKED_RANGES =
[
  IPAddr.new('0.0.0.0/8'),
  IPAddr.new('100.64.0.0/10')
].freeze

Class Method Summary collapse

Class Method Details

.parse_http_uri!(url, label) ⇒ Object

Raises:



54
55
56
57
58
59
# File 'lib/legate/auth/url_guard.rb', line 54

def parse_http_uri!(url, label)
  uri = URI.parse(url.to_s)
  return uri if uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)

  raise Legate::Auth::Error, "#{label} must use http or https scheme"
end

.resolved_ips(hostname) ⇒ Array<String>

Returns resolved IP strings ([] when resolution fails —the caller treats empty as a hard failure / fail-closed).

Returns:

  • (Array<String>)

    resolved IP strings ([] when resolution fails —the caller treats empty as a hard failure / fail-closed).



63
64
65
66
67
68
69
70
71
# File 'lib/legate/auth/url_guard.rb', line 63

def resolved_ips(hostname)
  [IPAddr.new(hostname).to_s]
rescue IPAddr::InvalidAddressError
  begin
    Resolv.getaddresses(hostname)
  rescue Resolv::ResolvError
    []
  end
end

.restricted?(ip) ⇒ Boolean

Returns:

  • (Boolean)


73
74
75
76
77
78
# File 'lib/legate/auth/url_guard.rb', line 73

def restricted?(ip)
  # Normalize IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1) to its IPv4 form so
  # the loopback/private/link-local checks aren't bypassed by the mapping.
  ip = ip.native if ip.ipv4_mapped?
  ip.loopback? || ip.link_local? || ip.private? || BLOCKED_RANGES.any? { |r| r.include?(ip) }
end

.validate!(url, label: 'Auth URL') ⇒ Object

Parameters:

  • url (String)

    The URL to validate

  • label (String) (defaults to: 'Auth URL')

    A label used in error messages

Raises:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/legate/auth/url_guard.rb', line 28

def validate!(url, label: 'Auth URL')
  return if ENV['LEGATE_ALLOW_PRIVATE_AUTH_URLS']

  hostname = parse_http_uri!(url, label).host
  ips = resolved_ips(hostname)
  # Fail closed: if we can't resolve the host, refuse rather than letting
  # the request through (an unresolvable host can't be checked, and a
  # resolver discrepancy could otherwise be used to slip past the guard).
  if ips.empty?
    raise Legate::Auth::Error,
          "#{label}: could not resolve host '#{hostname}' for SSRF validation."
  end

  ips.each do |ip_str|
    ip = IPAddr.new(ip_str)
    next unless restricted?(ip)

    raise Legate::Auth::Error,
          "#{label} resolves to restricted network address (#{hostname} -> #{ip_str}). " \
          'Set LEGATE_ALLOW_PRIVATE_AUTH_URLS=1 for development.'
  rescue IPAddr::InvalidAddressError
    next # skip unparseable IPs from the resolver
  end
end