Created
April 13, 2016 15:20
-
-
Save kirkchris/46c84f7fbe9b89fe461def3f4af34d4d to your computer and use it in GitHub Desktop.
Rails 4 Middleware which extends RemoteIp to provide the correct IP for distributions which are deployed as origins to CloudFront
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
module ApplicationName | |
class Application < Rails::Application | |
# Settings in config/environments/* take precedence over those specified here. | |
# Application configuration should go into files in config/initializers | |
# -- all .rb files in that directory are automatically loaded. | |
config.middleware.insert_before ActionDispatch::RemoteIp, "CloudFrontRemoteIp" | |
config.middleware.delete "ActionDispatch::RemoteIp" | |
end |
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
# Note: This code changes in rails 5, so make sure we update before then! | |
# Written by Chris Kirk at Primer (https://goprimer.com) | |
class CloudFrontRemoteIp < ActionDispatch::RemoteIp | |
def call(env) | |
env["action_dispatch.remote_ip"] = CloudFrontGetIp.new(env, self) | |
@app.call(env) | |
end | |
class CloudFrontGetIp < GetIp | |
def calculate_ip | |
# Set by the Rack web server, this is a single value. | |
remote_addr = ips_from('REMOTE_ADDR').last | |
# Could be a CSV list and/or repeated headers that were concatenated. | |
client_ips = ips_from('HTTP_CLIENT_IP').reverse | |
forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse | |
# only change needed from original code of this function to work with cloudfront | |
# original code here: https://github.com/rails/rails/blob/a59a9b7f729870de6c9282bd8e2a7ed7f86fc868/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L109 | |
# remove cloudfront ip | |
remove_cloudfront_ip(forwarded_ips) | |
# end of changes from original calculate_ip | |
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. | |
# If they are both set, it means that this request passed through two | |
# proxies with incompatible IP header conventions, and there is no way | |
# for us to determine which header is the right one after the fact. | |
# Since we have no idea, we give up and explode. | |
should_check_ip = @check_ip && client_ips.last && forwarded_ips.last | |
if should_check_ip && !forwarded_ips.include?(client_ips.last) | |
# We don't know which came from the proxy, and which from the user | |
raise ActionDispatch::RemoteIp::IpSpoofAttackError, "IP spoofing attack?! " \ | |
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " \ | |
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" | |
end | |
# We assume these things about the IP headers: | |
# | |
# - X-Forwarded-For will be a list of IPs, one per proxy, or blank | |
# - Client-Ip is propagated from the outermost proxy, or is blank | |
# - REMOTE_ADDR will be the IP that made the request to Rack | |
ips = [forwarded_ips, client_ips, remote_addr].flatten.compact | |
# If every single IP option is in the trusted list, just return REMOTE_ADDR | |
filter_proxies(ips).first || remote_addr | |
end | |
# supporting method has been added | |
def remove_cloudfront_ip(forwarded_ips) | |
return unless Rails.env.production? || Rails.env.staging? | |
forwarded_ips.shift | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment