Module: Surfguard

Extended by:
Surfguard
Included in:
Surfguard
Defined in:
lib/surfguard.rb,
lib/surfguard/version.rb

Overview

Resolve and classify addresses for callers that pin the selected address at connection time. Surfguard deliberately does not perform HTTP requests.

Defined Under Namespace

Classes: Unresolvable, Violation

Constant Summary collapse

UNRESOLVABLE_MESSAGE =
"Host could not be resolved"
BLOCKED_MESSAGE =
"Refusing blocked address"
MALFORMED_MESSAGE =
"Refusing malformed address"
MAX_HOST_BYTES =
255
MAX_ADDRESSES =
256
POLICIES =
%i[default iana_special_use].freeze
IANA_ALLOCATED_IPV6_UNICAST =

iana-generator:begin IANA_ALLOCATED_IPV6_UNICAST Generated from IANA IPv6 Global Unicast Status=ALLOCATED rows. Source provenance is checked in under script/iana.

%w[
  2001::/23 2001:200::/23 2001:400::/23 2001:600::/23 2001:800::/22
  2001:c00::/23 2001:e00::/23 2001:1200::/23 2001:1400::/22 2001:1800::/23
  2001:1a00::/23 2001:1c00::/22 2001:2000::/19 2001:4000::/23 2001:4200::/23
  2001:4400::/23 2001:4600::/23 2001:4800::/23 2001:4a00::/23 2001:4c00::/23
  2001:5000::/20 2001:8000::/19 2001:a000::/20 2001:b000::/20 2002::/16
  2003::/18 2400::/12 2410::/12 2600::/12 2610::/23 2620::/23 2630::/12
  2800::/12 2a00::/12 2a10::/12 2c00::/12
].map { |cidr| IPAddr.new(cidr).freeze }.freeze
DISALLOWED_IPV4 =

iana-generator:end IANA_ALLOCATED_IPV6_UNICAST

%w[
  0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8
  168.63.129.16/32 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24
  192.0.2.0/24 192.88.99.0/24 192.168.0.0/16 198.18.0.0/15
  198.51.100.0/24 203.0.113.0/24 224.0.0.0/4 240.0.0.0/4
].map { |cidr| IPAddr.new(cidr).freeze }.freeze
DISALLOWED_IPV6 =
%w[
  ::/128 100::/64 100:0:0:1::/64 2001::/32 2001:2::/48
  2001:db8::/32 2002::/16 3fff::/20 5f00::/16 fec0::/10 ff00::/8
].map { |cidr| IPAddr.new(cidr).freeze }.freeze
IANA_SPECIAL_USE_IPV4 =

iana-generator:begin IANA_SPECIAL_USE_IPV4 Every prefix in the checked-in IANA IPv4 special-purpose snapshot.

%w[
  0.0.0.0/8 0.0.0.0/32 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16
  172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.0.8/32 192.0.0.9/32
  192.0.0.10/32 192.0.0.170/32 192.0.0.171/32 192.0.2.0/24 192.31.196.0/24
  192.52.193.0/24 192.88.99.0/24 192.88.99.2/32 192.168.0.0/16
  192.175.48.0/24 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 240.0.0.0/4
  255.255.255.255/32
].map { |cidr| IPAddr.new(cidr).freeze }.freeze
IANA_SPECIAL_USE_IPV6 =

iana-generator:begin IANA_SPECIAL_USE_IPV6 Every prefix in the checked-in IANA IPv6 special-purpose snapshot.

%w[
  ::1/128 ::/128 ::ffff:0:0/96 64:ff9b::/96 64:ff9b:1::/48 100::/64
  100:0:0:1::/64 2001::/23 2001::/32 2001:1::1/128 2001:1::2/128
  2001:1::3/128 2001:2::/48 2001:3::/32 2001:4:112::/48 2001:10::/28
  2001:20::/28 2001:30::/28 2001:db8::/32 2002::/16 2620:4f:8000::/48
  3fff::/20 5f00::/16 fc00::/7 fe80::/10
].map { |cidr| IPAddr.new(cidr).freeze }.freeze
IETF_PROTOCOL_ASSIGNMENTS =

iana-generator:end IANA_SPECIAL_USE_IPV6

IPAddr.new("2001::/23").freeze
GLOBALLY_REACHABLE_IETF_ASSIGNMENTS =
%w[
  2001:3::/32 2001:4:112::/48
].map { |cidr| IPAddr.new(cidr).freeze }.freeze
NAT64_WELL_KNOWN =
IPAddr.new("64:ff9b::/96").freeze
NAT64_LOCAL_USE =
IPAddr.new("64:ff9b:1::/48").freeze
IPV4_TRANSLATABLE =
IPAddr.new("::ffff:0:0:0/96").freeze
IPV4_COMPATIBLE =
IPAddr.new("::/96").freeze
POLICY_RANGES =
{
  default: {
    allocated_ipv6: IANA_ALLOCATED_IPV6_UNICAST,
    disallowed_ipv4: DISALLOWED_IPV4,
    disallowed_ipv6: DISALLOWED_IPV6
  }.freeze,
  iana_special_use: {
    ipv4: IANA_SPECIAL_USE_IPV4,
    ipv6: IANA_SPECIAL_USE_IPV6
  }.freeze
}.freeze
VERSION =
"0.2.0"

Instance Method Summary collapse

Instance Method Details

#blocked_address?(ip, policy: :default) ⇒ Boolean

Classify one endpoint. Malformed inputs and networks fail closed.

Returns:

  • (Boolean)


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/surfguard.rb', line 145

def blocked_address?(ip, policy: :default)
  validate_policy!(policy)
  ipaddr = normalize_ip(ip)

  return true if policy == :iana_special_use && iana_special_use?(ipaddr)
  return true if ipaddr.ipv4_mapped? || IPV4_COMPATIBLE.include?(ipaddr)
  return disallowed_ipv4?(ipaddr) if ipaddr.ipv4?
  return true if NAT64_LOCAL_USE.include?(ipaddr)

  if NAT64_WELL_KNOWN.include?(ipaddr) || IPV4_TRANSLATABLE.include?(ipaddr)
    return disallowed_ipv4?(embedded_ipv4(ipaddr), policy: policy)
  end

  disallowed_ipv6?(ipaddr)
rescue InvalidInput
  true
end

#enforce_public_ip(url, policy: :default) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/surfguard.rb', line 120

def enforce_public_ip(url, policy: :default)
  validate_policy!(policy)
  addresses = resolve(host_of(normalize_url(url)))
  if addresses.any? { |ip| blocked_address?(ip, policy: policy) }
    raise Violation, BLOCKED_MESSAGE, cause: nil
  end

  nil
rescue InvalidInput
  raise Violation, MALFORMED_MESSAGE, cause: nil
end

#resolvable_public_ip?(url, policy: :default) ⇒ Boolean

True only when the URL resolves and every answer is admitted.

Returns:

  • (Boolean)


112
113
114
115
116
117
118
# File 'lib/surfguard.rb', line 112

def resolvable_public_ip?(url, policy: :default)
  validate_policy!(policy)
  addresses = resolve(host_of(normalize_url(url)))
  addresses.none? { |ip| blocked_address?(ip, policy: policy) }
rescue InvalidInput, Unresolvable
  false
end

#resolve_public_ip(url, policy: :default) ⇒ Object

Preserve resolver order here; unlike the plural API this method does not reorder address families.



134
135
136
137
138
139
140
141
142
# File 'lib/surfguard.rb', line 134

def resolve_public_ip(url, policy: :default)
  validate_policy!(policy)
  addresses = resolve(host_of(normalize_url(url)))
  return nil if addresses.any? { |ip| blocked_address?(ip, policy: policy) }

  addresses.first.to_s.freeze
rescue InvalidInput
  nil
end

#resolve_public_ips(host, policy: :default) ⇒ Object

Return every admitted address, with IPv4 before IPv6 and resolver order retained within each family. Malformed direct input returns [].



101
102
103
104
105
106
107
108
109
# File 'lib/surfguard.rb', line 101

def resolve_public_ips(host, policy: :default)
  validate_policy!(policy)
  addresses = resolve(normalize_host(host))
  public_addresses = addresses.reject { |ip| blocked_address?(ip, policy: policy) }
  ipv4, ipv6 = public_addresses.partition(&:ipv4?)
  (ipv4 + ipv6).map { |ip| ip.to_s.freeze }.freeze
rescue InvalidInput
  [].freeze
end