Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloudfront forwarded proto header #2089

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rack.rb
Expand Up @@ -48,6 +48,7 @@ module Rack
autoload :Runtime, "rack/runtime"
autoload :Sendfile, "rack/sendfile"
autoload :Server, "rack/server"
autoload :SetXForwardedProtoHeader, "rack/set_x_forwarded_proto_header"
autoload :ShowExceptions, "rack/show_exceptions"
autoload :ShowStatus, "rack/show_status"
autoload :Static, "rack/static"
Expand Down
44 changes: 44 additions & 0 deletions lib/rack/set_x_forwarded_proto_header.rb
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Rack

# Middleware to set the X-Forwarded-Proto header to the value
# of another header.
#
# This header can be used to ensure the scheme matches when comparing
# request.origin and request.base_url for CSRF checking, but Rack
# expects that value to be in the X_FORWARDED_PROTO header.
#
# For example, AWS Cloudfront sets a CloudFront-Forwarded-Proto header
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html#cloudfront-headers-other
tomharvey marked this conversation as resolved.
Show resolved Hide resolved
#
# Example Rails usage with AWS Cloudfront:
# In this AWS example, add
#`config.middleware.use Rack::SetXForwardedProtoHeader, 'CloudFront-Forwarded-Proto'`
# to your application.rb file

class SetXForwardedProtoHeader
def initialize(app, vendor_forwarded_header)
@app = app
@vendor_forwarded_header = standard_header vendor_forwarded_header
tomharvey marked this conversation as resolved.
Show resolved Hide resolved
end

def call(env)
return @app.call(env) unless env[@vendor_forwarded_header]
tomharvey marked this conversation as resolved.
Show resolved Hide resolved
copy_header_value(env)
@app.call(env)
end

protected

def copy_header_value(env)
env["HTTP_X_FORWARDED_PROTO"] = env[@vendor_forwarded_header]
end

def standard_header(header)
# Rack expects to see UPPER_UNDERSCORED_HEADERS, never SnakeCased-Dashed-Headers
upper_underscored = header.upcase.gsub "-", "_"
return "HTTP_#{upper_underscored}"
end
end
end
43 changes: 43 additions & 0 deletions test/spec_set_x_forwarded_proto_header.rb
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require_relative 'helper'


describe Rack::SetXForwardedProtoHeader do
response = lambda {|e| [200, {}, []] }
jeremyevans marked this conversation as resolved.
Show resolved Hide resolved

it "leaves the value of X_FORWARDED_PROTO intact if there is no vendor header passed in the request" do
vendor_forwarded_header = "not passed in the request"
env = Rack::MockRequest.env_for("/", "HTTP_X_FORWARDED_PROTO" => "http")

Rack::Lint.new(Rack::SetXForwardedProtoHeader.new(response, vendor_forwarded_header)).call env

env["HTTP_X_FORWARDED_PROTO"].must_equal "http"
end

it "returns early when there is no vendor header passed in the request" do
vendor_forwarded_header = "not passed in the request"
env = Rack::MockRequest.env_for("/", "FOO" => "bar")

header_middleware = Rack::SetXForwardedProtoHeader.new(response, vendor_forwarded_header)
# Patch to ensure we return early and do not call `copy_header_value`
def header_middleware.copy_header_value
tomharvey marked this conversation as resolved.
Show resolved Hide resolved
raise NoMethodError, "should never be called when vendor_forwarded_header is not in the request"
end

Rack::Lint.new(header_middleware).call env

env["FOO"].must_equal "bar"
end


it "copies the value of the header to X-Forwarded-Proto" do
env = Rack::MockRequest.env_for("/", "HTTP_CLOUDFRONT_FORWARDED_PROTO" => "https")

Rack::Lint.new(Rack::SetXForwardedProtoHeader.new(response, "CloudFront-Forwarded-Proto")).call env

env["HTTP_X_FORWARDED_PROTO"].must_equal "https"
end

tomharvey marked this conversation as resolved.
Show resolved Hide resolved

end