This post originated from an RSS feed registered with Ruby Buzz
by Jonathan Weiss.
Original Post: Mongrel and Rails behind Apache 2.2 and SSL
Feed Title: BlogFish
Feed URL: http://blog.innerewut.de/feed/atom.xml
Feed Description: Weblog by Jonathan Weiss about Unix, BSD, security, Programming in Ruby, Ruby on Rails and Agile Development.
After the initial plain HTTP setup was working fine we went on to configure HTTPS. The obvious way is to configure an Apache SSL virtual host, that proxies all requests to the Mongrel cluster (for more on how to setup the Mongrel cluster look here).
This setup works fine until you initiate an internal redirect in your rails code like this:
redirect_to :action => 'list'
As Rails does not know that is behind an HTTPS proxy it creates a redirection to a HTTP resource. This breaks your security and e.g. results in IE complaining about unsafe file transmission on POSTs. James Duncan Davidson has a nice solution for this annoyance.
The solution is to tell Rails that it is operated in HTTPS mode without breaking the development environment. This can be done by setting an environment variable with Apache in the request and checking for this variable in a before filter. If this variable is set, redirect to HTTPS resources. Otherwise use plain old HTTP.
In order to set an environment variable in Apache, include the following line in the SSL virtual host definition:
RequestHeader set X_ORIGINAL_PROTOCOL 'https'
Now create a before_filter in the ApplicationController that checks for this variable:
before_filter :set_ssl
...
def set_ssl
if request.env.has_key? 'HTTP_X_ORIGINAL_PROTOCOL'
if request.env['HTTP_X_ORIGINAL_PROTOCOL'] == "https"
request.env["HTTPS"] = "on"
end
end
end
request.env["HTTPS"] = "on" tells Rails to consider the request as an HTTPS request and therefore generate redirects that obey this.
One thing to watch out for is that the variable gets a "HTTP_" prefix set by Apache. So we set the variable "X_ORIGINAL_PROTOCOL" but check for "HTTP_X_ORIGINAL_PROTOCOL".
Knowing this can save you some hours of debugging...
UPDATE:
After poking around in the ActionController sources there seems to be a much better and easier way. Just set this variable (in httpd.conf) and delete the before_filter:
RequestHeader set X_FORWARDED_PROTO 'https'
Rails will figure out the rest itself. The magic comes from these lines in request.rb:
def ssl?
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
end