For an application I'm writing, I needed a way to log in without having to enter a password. You can authenticate a client against a server in Apache using SSL client certificate validation. The problem was to create a link between Apache and the Rails application running under Mongrel. Here is what I've done.

Apache and SSL

First, you'll need Apache set up with SSL and client certificate validation. There are howtos on the web on how to to that, so I won't explain that in full details here. You'll need a server private key and certificate and a client certificate issued by the same authority (or authorities trusting each others). For myself, I use CAcert which provides free "ethic" certificates.

I use Coda Hale great article to configure my rails apps. Here is what I added to the Apache configuration for having it working with SSL and client certificate validation. Please note that only the lines added or changed are listed here. For it to work, you need to enable the Apache 2 SSL and headers modules.

<virtualhost>
  SSLEngine on
  SSLCertificateFile /path/to/cert.pem
  SSLCertificateKeyFile /path/to/private.key
  SSLCACertificateFile /path/to/cacert.pem
  SSLVerifyClient none

  <location>
    SSLVerifyClient require
    SSLVerifyDepth 1
  </location>

  RequestHeader set X_FORWARDED_PROTO 'https'
  RequestHeader set X_SSL_CLIENT_DN_Email "%{SSL_CLIENT_S_DN_Email}s"
</virtualhost>

So what's the meaning of all this ?

On line 1, we say that this virtual host is for connection on port 443 (don't forget to add a "Listen 443" directive to your Apache config).

Line 2 to 5, we simply configure server side ssl.

On line 6 to 12, we configure client certificate validation. The line 6 is usefull if you want to require validation only for some specific locations (say 'admin' for example). Line 9, we require client certificate validation for the '/' location. Line 10 says that we need to go up in the authority hierarchy of 1 level maximum (my client and server certificates are issued by the same authority).

On line 13, we tell rails that we are using the "https" protocol so that links outputted by call to "link_to", "url_for" and the likes need to start with "https" instead of "http".

SSL, Rails and Mongrel

This is the interesting part, specific to using Rails with Mongrel.

On line 14, we say to Apache to add the "X_SSL_CLIENT_DN_Email" header to the value of the "DN_Email" field in the SSL client certificate. This is the only way I have found to forward to Mongrel the SSL client certifcate values that are of interest to us.

Now in your controller, to fetch the email of the user connected:

1
2
3
4
5
6
7

class ApplicationController < ActionController::Base
  before_filter :ssl_login
  def ssl_login
      session[:user] =  User.find_by_email(request.env["HTTP_X_SSL_CLIENT_DN_EMAIL"])
    end
  end
end

If your Mongrel instances are only listening to your local host network interface (think 127.0.0.1), this should be secure:

  • Apache will prevent anybody not having a valid client certificate to even reach your Mongrel instances,
  • for the others, Apache sets the email of the certificate in a header and overwrite the header if already present (so client are unable to forge that header),
  • assuming a common authentication scheme, if the user email is in your database, he is allowed to connect.

Please do note that if a hacker is able to connect to your Mongrel instances via your local host network interface, he can forge the header and pretend to be someone else. But if he has access to your local host interface, you probably are already in big troubles.

I hope you found this useful, any comments are of course welcome.