Inter-Service Authentication with SSL
At Collective Idea, we love building web services. Oftentimes we also build the client applications that consume those services.
One of the major challenges with a service-oriented architecture is authenticating communication between the client and the service as well as between services.
Oh, You Mean OAuth!
OAuth is a good candidate for this type of authentication. It provides flexibility in terms of scoped authorization levels, securely signed requests, token expiration and all sorts of other goodies. And these things are great, especially for public-facing applications, where anybody could register through your OAuth provider.
But if you're only dealing with a small number of controlled, trusted clients, implementing an OAuth provider for each service might be a bit much.
Every request to the services needs to identify its client application and more importantly, it needs to be verified. What we need is a tried and tested way to communicate trust.
We usually see SSL in the form of an "https://" URL in our browser, and a little lock icon to make us feel all warm and fuzzy. That feeling of security comes from the fact that our browser has identified the website and we can trust that they actually are who they say they are.
This sounds awfully close to what we're looking for. If an SSL certificate can verify the recipient of a request, we should be able to reverse the process to verify the sender of a request.
To set up our SSL authentication, each client application needs its own SSL certificate. So first thing's first: we need a private key with which to sign all of these SSL certificates… one key to rule them all. Signing all of the client certificates with the same private key will allow us to verify their authenticity later.
openssl genrsa -out master.key 1024
Now that we have our master key, we can start generating SSL certificates. Each certificate will require a new CSR (certificate signing request). CSR generation will prompt for information about the certificate and we'll take that opportunity to identify the client application that this certificate belongs to.
# Enter "web-client" for "Organizational Unit Name." # The remaining attributes aren't important. openssl req -new -key master.key -out web-client.csr
With the CSR in hand, generating a signed certificate is easy.
openssl x509 -req -in web-client.csr -signkey master.key -out web-client.crt
This certificate is given to the client application and every request originating from the client will include the contents of that certificate in a custom header.
Meanwhile, in the service… when a request is received we'll need to assert that the certificate provided is authentic. We do this by verifying that the certificate is signed by the master key. The best part is that the service doesn't even need the private, master key to do that verification. It can use a public key derived from the master key instead.
openssl rsa -in master.key -pubout > master.pub
Every service gets this same public, master key and uses it to verify all incoming requests.
Assuming your service is written in Rails, here's how that verification might be implemented:
class ApplicationController < ActionController::Base before_filter :require_authentication private def require_authentication unless current_certificate.verify(public_key) head :forbidden end end def public_key @public_key ||= OpenSSL::PKey::RSA.new(ENV['AUTH_PUBLIC_KEY']) end def current_certificate @current_certificate ||= OpenSSL::X509::Certificate.new(request.headers['X-SSL-Auth']) end def current_client current_certificate.issuer.to_a.assoc('OU') end end
The public key used for verification is kept in the server's environment at
ENV\['AUTH\_PUBLIC\_KEY'\] and the current request's certificate is pulled from the headers.
The certificate responds to the
verify method which accepts the public key and simply returns
true if the certificate is authentic or
You'll notice the
current\_client method as well, which extracts the organizational unit from the certificate that we set during the CSR generation. This identifies the client application making the request. With that information, the service can implement any sort of access controls that may (or may not) be required.
In the Wild
At Collective Idea, we think SSL certificate authentication is a cool idea and in some cases, a great fit. It's also comforting to know that Square uses this same approach for authentication between its own services.
In fact, this post was inspired by a talk by Square's Chris Hunt at RubyConf 2012. His talk omits some of the details of certificate generation and verification but it's a fantastic resource for good service-oriented architecture.