Skip to content

Instantly share code, notes, and snippets.

@bjeanes
Created October 14, 2016 04:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjeanes/c41ef2dab888c078d63b4f5cc3502b9f to your computer and use it in GitHub Desktop.
Save bjeanes/c41ef2dab888c078d63b4f5cc3502b9f to your computer and use it in GitHub Desktop.
ugh
require 'ipaddr'
require 'json'
require 'net/https'
require 'uri'
module CloudfrontIPs
IP_LIST = URI.parse('https://ip-ranges.amazonaws.com/ip-ranges.json')
class << self
# For Rails 4, when we have it, this is sufficient for their whitelisting.
def ranges
@@ranges ||= get_cdn_ips
end
# For Rails 3 😞 trusted proxies are defined by a Regexp, so we haveto
# explode the IPAddr class into an array of IPAddr (one for every item in
# the range), get the string representation of it, and build a Regexp that
# matches anything in that range.
#
# To make the Regexp a bit smaller (and, I think, a bit faster) we match
# the common prefix of everything in the range first, and check the
# (looong) list of addresses only if the prefix matches.
#
# This returns a single regex per range (there are 20 published ranges at
# time of writing).
def regexes
ranges.flat_map do |range|
ips = range.to_range.to_a.map(&:to_s)
prefix = Regexp.escape(lcp(ips))
suffixes = ips.map { |ip| ip.sub(/^#{prefix}/,'') }
/^#{prefix}#{Regexp.union(suffixes)}$/
end
end
# Combine the above regexes into a single one.
def regex
@@regex ||= Regexp.union(regexes)
end
def load
Kernel.load(file_name)
REGEXP
end
def dump
File.open(file_name, 'w') do |f|
f.write(<<-FILE)
module CloudfrontIPs
REGEXP = #{regex.inspect}
end
FILE
end
end
private
def file_name
File.expand_path(__FILE__).sub(/\.rb$/, '/regexp.rb')
end
# Longest common prefix of a string
#
# There are something like ~600k unique CDN IPs. The idea here is to make
# the regexp _substantially_ smaller by only checking for the shared prefix
# a single time in any given range.
def lcp(strs)
return "" if strs.empty?
min, max = strs.minmax
idx = min.size.times{|i| break i if min[i] != max[i]}
min[0...idx]
end
def get_cdn_ips
http = Net::HTTP.new(IP_LIST.host, IP_LIST.port)
http.use_ssl = IP_LIST.scheme == 'https'
request = Net::HTTP::Get.new(IP_LIST.request_uri)
response = http.request(request)
if response.code[0] == '2'
body = JSON.load(response.body)
body["prefixes"]
.select { |p| p["service"] == "CLOUDFRONT" }
.map { |p| IPAddr.new(p["ip_prefix"]) }
else
[]
end
end
end
end
@bjeanes
Copy link
Author

bjeanes commented Oct 20, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment