Created
October 14, 2016 04:55
-
-
Save bjeanes/c41ef2dab888c078d63b4f5cc3502b9f to your computer and use it in GitHub Desktop.
ugh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/bjeanes/12f516fb0a573ea60db8e4657fb65efb