I now have quite a lot of software infrastructure supporting the business of tracking things that move; particularly aircraft flights. The back-bone of all of this is ActiveMQ. ActiveMQ is an incredible messaging work-horse and easily handles 300 messages per second being received from a number of radar sensors.
I have also re-developed my Titan Class Vision client application using
JavaFX (the subject of another blog entry). The goal of this re-write was to be able to deploy Vision on a wider variety of platforms than I could do so previously. Before the JavaFX version Vision was a turn-key hardware/software combination written in Objective-C/C++ and ran on Mac OS X only.
With the advent of the new version I am able to deploy Vision across the internet. Given this there are suddenly many more potential candidates in terms of users and I had a security question to consider: how can I demonstrate to whoever is concerned that I have made every effort to ensure that this sensitive real-time flight data is not being mis-used by anyone.
Authentication
My requirement therefore became one centered around SSL. ActiveMQ permits connections to be established using SSL. Server only authentication is fairly straightforward and
covered here. What I was after though was client certificate verification; otherwise known as
mutual SSL authentication. In a nutshell, the server verifies the client's certificate as one it trusts, and the client verifies the server's certificate as one it trusts.
When it comes to the client application, in my case the JavaFX application, you need to make sure that the client's keystore is accessible. Don't do as I did and try using the JRE's default keystore for this purpose. I just couldn't get that to work. Instead do as the
ActiveMQ SSL page suggests and provide the client with its own keystore.
Another tip for the client, is to set the javax.net.ssl.* properties within the application itself; before you try establishing the JMS connection of course. I express the location of the client's keystore in relation to the user.home system property.
From a broker (server) perspective, one has to use a
JAAS LoginModule that permits certificate based authentication. Fortunately I found an ActiveMQ-JAAS class named
CertificateLoginModule (of all things).
One very subtle thing to note: when specifying the use of this login module in activemq.xml you must use the jaasCertificateAuthenticationPlugin element instead of the jaasAuthenticationElement. I think that this is because the certificate login module requires a different login callback to obtain the client's certificate.
CertificateLoginModule is only half the picture in the same way that authentication is only half the picture. Authorisation is required and the CertificateLoginModule has to be extended to support this; the login module does not know how to authorise a certificate. I can help there as I have provided this code. More on that later though (I insist on you reading the rest of this entry!).
Finally on authentication, you need to tell ActiveMQ that you want to perform client certificate authentication; it will not do it unless told to do so. You do this by specifying "needClientAuth=true" parameter on the ssl transport in activemq.xml.
You might want to disable the other connectors and open up your firewall just for 61617 SSL connectivity. I think that once you make authentication a priority with the broker then you need to give up the non-secure connectors.
Authorisation
With authentication done (I know who you are, you know who I am), I needed to deal with authorisation (now I know who you are, what am I going to allow you to do). For this I wanted to centralise my user and group/role information. ActiveMQ allows you to specify user/group associations in its configuration file, but I wanted to do what all grown-ups do: specify my users and groups in a centralised LDAP directory.
The CertificateLoginModule requires extension to specify how authorisation is done. I have created a
CertificateLoginDirectoryRolesModule that will take the subject DN from each client certificate presented (there can be many but typically just one), and then call upon my LDAP store to determine which groups the DN is a member of.
I have set up my LDAP server (
ApacheDS - fabulous) to allow anonymous access but also enabled access controls. This means that, by default, the LDAP server permits very little authorisation with just admin access. I then created a group named "activemq" off the "groups" node and used an ACI to allow anonymous searching of that group. I ended up with a group hierarchy as follows:
ou=system
ou=groups
ou=activemq (anonymous users can see this and below)
cn=jms-services
cn=activemq-users
cn=com.classactionpl.javaFlightTopic.Subscribers
Correspondingly here is my authorisation mapping within activemq.xml:
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue=">"
read="jms-services"
write="jms-services"
admin="jms-services" />
<authorizationEntry topic=">"
read="jms-services"
write="jms-services"
admin="jms-services" />
<authorizationEntry topic="com.classactionpl.javaFlightTopic"
read="com.classactionpl.javaFlightTopic.Subscribers" />
<authorizationEntry topic="ActiveMQ.Advisory.>"
read="activemq-users"
write="activemq-users"
admin="activemq-users" />
</authorizationEntries>
</authorizationMap>
</map>
</authorizationPlugin>
What the above states is that jms service providers, such as my Camel based applications, can effectively publish and subscribe to anything. However my client belongs to the javaFlightTopic.Subscribers group and the activemq-users group and so can only consume from a specific topic and perform all required advisory services; the latter being an ActiveMQ requirement.
It is possible to express the authorisation mappings in an LDAP store as well. We'll see if the need surfaces.
Source Code
I have created an open-source project named
jaasloginmodules that hosts this JAAS login module and have tested and used the classes. The CertificateLoginDirectoryRolesModule is ready for
download and use.