Wednesday, November 28, 2007

Testing Rails SSL Requirements on Your Development Machine

Update 1/31/2012: This advice is pretty old but should still basically work. I now prefer a more flexible solution using Unicorn and Pound.


I am building a Rails app that requires some portions of the site to use HTTPS, so naturally I'm using the SSL requirement plugin. The plugin works great, but if you're using Mongrel or WEBrick running out of script/server in your development environment, you now won't be able to talk to those parts of your site (since these servers do not include SSL encryption).

The solution is pretty easy, but it's not something I found written up elsewhere, so I thought I'd document it here. All you have to do is install your own local Apache server and have it proxy requests to the Mongrel or WEBrick instance, similar to how you would set up your production environment. For simplicity I didn't use a cluster of mongrels, or mod_balance, or anything like that, just a straight-through proxy. See "A Simple Single Mongrel Configuration" on the Mongrel site for details.

But there's a bit more you need to do in order to make things work with Rails and the SSL requirement plugin.  Below is a subset of my httpd.conf file; for clarity, I cut out all the default settings and just left what I added or changed.
Listen 80 # included in the default config
Listen 443 # Apache needs to know you want to accept connections over HTTPS
SSLCertificateFile /usr/local/apache/conf/newcert.pem
SSLCertificateKeyFile /usr/local/apache/conf/newkey.pem
# Below is optional, but was helpful to me in debugging this setup
CustomLog logs/ssl_request_log  "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<VirtualHost *:80>ServerName localhost
 ServerAlias 127.0.0.1
 ProxyPass / http://localhost:3000/
 ProxyPassReverse / http://localhost:3000
 ProxyPreserveHost on</VirtualHost>
<VirtualHost *:443>SSLEngine On
ServerName localhost
ServerAlias 127.0.0.1
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000
ProxyPreserveHost on
RequestHeader set X_FORWARDED_PROTO 'https' # don't forget this line!
</VirtualHost>

<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>
What you're doing is setting up two different proxies through Apache to your Mongrel server; one via port 80, unencrypted, and one via port 443, encrypted with SSL.  If you don't include the X_FORWARDED_PROTO line in your 443 virtual host, Rails won't know that it's using SSL and the SSL requirement filter will fail.

The two SSLcertificate directives refer to the cryptographic data needed to encrypt the traffic. I just made some self-signed certificates (which are free).  There are a million tutorials out there; I used the one on Apple's site.  You'll need to pick a passphrase to protect your private key.  Pick something short (since this isn't the production site) because you'll need to enter it every time you want to reboot your server.

Start things up and you're ready to go!

wymanpark~ $ sudo httpd -k start
Apache/2.2.6 mod_ssl/2.2.6 (Pass Phrase Dialog)
Some of your private key files are encrypted for security reasons.
In order to read them you have to provide the pass phrases.

Server localhost:443 (RSA)
Enter pass phrase:

OK: Pass Phrase Dialog successful.
wymanpark~ $ 

15 comments:

Adam said...

Looks like ProxyPreserveHost and
RequestHeader are only features of Apache 2.0+. Any ideas how to upgrade Apache on Mac OS X?

Mike said...

Adam,

I install everything from source, so I actually have the latest apache running from /usr/local/apache. You can compile it with the following options:

./configure --enable-deflate --enable-proxy --enable-proxy-html \
--enable-proxy-balancer --enable-rewrite --enable-cache \
--enable-mem-cache --enable-ssl --enable-headers

Just make sure you have XCode developer tools installed. You can get the source code from here:

http://httpd.apache.org/download.cgi

hcir said...

This helped me to get one rails app running via apache but i have not been able to get two apps running at the same time, any suggestions.

Thanks

Mike said...

hcir,

You might try creating a different VirtualHost entry on a different port (like *:8080) and have it point to a separate mongrel.

I've also had good luc using the NameVirtualHost directive, which will let you create VirtualHost entries that respond to different server names, but this means you also need to mess with your /etc/hosts file. Here's a great tutorial:

http://theappleblog.com/2006/11/21/how-to-setup-development-domains-on-os-x/

ak said...

seems easier to just modify the code to ignore the requirement in development mode. i will post the mod if go about it that way.

Mike said...

Ak, for this app, there are a lot of requirements for certain functions to use SSL and others to not use SSL, so I really wanted to give the system a workout in an environment close to the real thing. When I was in the Navy we used to say "train like you fight".

Bill said...

Good post -- I've created a related verison on my blog that goes into more details on the specifics of setting up certificates and walking through the process of getting Apache working with SSL from front to back. It lives here if anyone is interested:

http://www.williambharding.com/blog?p=120

Mr Frosti said...

"RequestHeader set X_FORWARDED_PROTO 'https' # don't forget this line!"

What a life saver! I have spent over an hour scratching my head, and muttering some choice words...

Mike Subelsky said...

thanks! glad I could help...sn

Jim B said...

Hi Mike,

I'm using the methods you suggest for accessing HTTPS elements of a rails site I'm writing locally, and its almost there.

But ......

Rails is "translating" a basic HTTP request to HTTPS and I'm now getting a "crossdomain.xml" issue.

I'm not sure how to fix this one, and any help would be appreciated.

Mike Subelsky said...

Jim, did you set the X_FORWARDED_PROTO header? Maybe there's some other header that needs to be set for your app.

Ric Roberts said...

Hey Mike.

This blog post really helped me out! Thanks.

Ric.

Mike Subelsky said...

Ric, very glad to hear it!

Pete said...

Very helpful, thanks.

FYI, when I copied your config, I had to remove the trailing comments from these two lines;

Listen 80 # included in the default config
Listen 443 # Apache needs to know you want to accept connections over HTTP

Otherwise the console showed syntax errors on start.

bourne111 said...

supra shoes
air max shoes
nike outlet
nike basketball shoes
lebron james shoes
kobe bryant shoes
nike air force
nike shox
nike sb