This article is based on ActiveMQ in Action, published 24-March-2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) ebooks and pbooks. MEAPs are sold exclusively through Manning.com. All print book purchases include an ebook free of charge. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information.
Building a custom security plugin
Introduction
While the built-in security features in ActiveMQ provide enough functionality for the majority of users, an even more powerful feature is available. The ActiveMQ plugin API is extremely flexible and the possibilities are endless. The flexibility in this functionality comes from the BrokerFilter class in ActiveMQ. This class provides the ability to intercept many of the available broker-level operations. Broker operations include such items as adding consumers and producers to the broker, committing transactions in the broker, and adding and removing connections to the broker, to name a few. Custom functionality can be added by extending the BrokerFilter class and overriding a method for a given operation.
While the ActiveMQ plugin API is not concerned solely with security, implementing a class whose main purpose is to handle a custom security feature is quite achievable. So, if you have security requirements that cannot be met using the previous security features, you may want to consider developing a custom solution for your needs. Depending on your needs, two choices are available:
- Implement a Java Authentication and Authorization Service (JAAS) login module—Chances are you’re already using JAAS in your Java applications. In this case, it’s only natural that you’ll try to reuse all that work for securing ActiveMQ broker.
- Implement a custom plugin for handling security—ActiveMQ provides a very flexible generic plugin mechanism. You can create your own custom plugins for just about anything, including the creation of a custom security plugin. So, if you have requirements that cannot be met by implementing a JAAS module, writing a custom plugin is the way to go.
We will describe how to write a simple security plugin that authorizes broker connections only from a certain set of IP addresses. The concept is not complex but is good enough to give you a taste of the BrokerFilter with focus on security.
Implementing the plugin
In order to limit connectivity to the broker based on the IP address, a class named IPAuthenticationBroker will be created to override the BrokerFilter.addConnection() method. The implementation of this method will perform a simple check of the IP address using a regular expression to determine the ability to connect. Below is the implementation of the IPAuthenticationBroker class:
public class IPAuthenticationBroker extends BrokerFilter { List<String> allowedIPAddresses; Pattern pattern = Pattern.compile("^/([0-9\\.]*):(.*)"); public IPAuthenticationBroker(Broker next, List<String> allowedIPAddresses) { super(next); this.allowedIPAddresses = allowedIPAddresses; } public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { #1 String remoteAddress = context.getConnection().getRemoteAddress(); Matcher matcher = pattern.matcher(remoteAddress); if (matcher.matches()) { String ip = matcher.group(1); if (!allowedIPAddresses.contains(ip)) { throw new SecurityException( "Connecting from IP address " + ip + " is not allowed" ); } } else { throw new SecurityException("Invalid remote address " + remoteAddress); } super.addConnection(context, info); } } #1 The BrokerFilter.addConnection() method is being overridden to provide the ability to filter based on IP address
The BrokerFilter class defines methods that intercept broker operations such as adding a connection, removing a subscriber, and so on. In the IPAuthenticationBroker class above, the addConnection() method is overridden to create some logic that checks if the address of a connecting client falls within a list of IP addresses that are allowed to connect. If that IP address is allowed to connect, the call is delegated to the BrokerFilter.addConnection() method. If that IP address is not allowed to connect, an exception is thrown.
One additional item of note in the IPAuthenticationBroker class is that its constructor calls the BrokerFilter’s constructor. This call serves to set up the chain of interceptors so that the proper cascading will take place through the chain. Don’t forget to do this if you create your own BrokerFilter implementation.
After the actual plugin logic has been implemented, the plugin must be configured and installed. For this purpose, an implementation of the BrokerPlugin will be created. The BrokerPlugin is used to expose the configuration of a plugin and also installs the plugin into the ActiveMQ broker. In order to configure and install the IPAuthenticationBroker, the IPAuthenticationPlugin class is created as shown below:
public class IPAuthenticationPlugin implements BrokerPlugin { List<String> allowedIPAddresses; public Broker installPlugin(Broker broker) throws Exception { #1 return new IPAuthenticationBroker(broker, allowedIPAddresses); } public List<String> getAllowedIPAddresses() { return allowedIPAddresses; } public void setAllowedIPAddresses(List<String> allowedIPAddresses) { this.allowedIPAddresses = allowedIPAddresses; } } #1 The installPlugin() method implementation is used to create an instance of the custom IPAuthenticationBroker class
The IPAuthenticationBroker.installPlugin() method is used to instantiate the plugin and return a new intercepted broker for the next plugin in the chain. Notice that the IPAuthenticationPlugin class also contains getter and setter methods used to configure the IPAuthenticationBroker. These setter and getter methods are then available via a Spring beans style XML configuration in the ActiveMQ XML configuration file (as we will see in a moment).
Configuring the plugin
Now that we have the plugin implemented, let’s see how we can configure it using ActiveMQ XML configuration file. Below is an example of how the IPAuthenticationPlugin class is used in the configuration:
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.base}/data" plugins="#ipAuthenticationPlugin"> #1 <transportConnectors> <transportConnector name="openwire" uri="tcp://localhost:61616" /> </transportConnectors> </broker> <bean id="ipAuthenticationPlugin" class="org.apache.activemq.book.ch6.IPAuthenticationPlugin"> <property name="allowedIPAddresses"> <list> <value>127.0.0.1</value> </list> </property> </bean> #2 #1 The <broker> element supports the plugins attribute as a shortcut #2 The Spring beans style configuration of the IPAuthenticationPlugin
The <broker> element provides the plugins attribute as a shortcut to declaring all plugins that do not utilize an XBean style of configuration and, therefore, cannot be configured inside the <plugins> element. Note that the id of the bean is used to refer to its definition from the plugins attribute. Using this configuration, only those clients connecting from the IP address 127.0.0.1 (in other words, the localhost) can actually connect to the broker.
Testing the plugin
All that needs to be done now is to test the plugin. Below is the command to start up ActiveMQ using the IPAuthenticationPlugin and the IPAuthenticationBroker:
${ACTIVEMQ_HOME}/bin/activemq \ xbean:src/main/resources/org/apache/activemq/book/ch6/activemq-custom.xml ... Loading message broker from: xbean:src/main/resources/org/apache/activemq/book/ch6/activemq- INFO | Using Persistence Adapter: AMQPersistenceAdapter(/workspace/apache-activemq-5.3.0/data/ INFO | AMQStore starting using directory: /workspace/apache-activemq-5.3.0/data/localhost INFO | Kaha Store using data directory /workspace/apache-activemq-5.3.0/data/localhost/kr- store/ INFO | Active data files: [] INFO | ActiveMQ 5.3.0 JMS Message Broker (localhost) is starting INFO | For help or more information please see: http://activemq.apache.org/ INFO | Kaha Store using data directory /workspace/apache-activemq-5.3.0/data/localhost/kr- store/ INFO | JMX consoles can connect to service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi INFO | Listening for connections at: tcp://localhost:61616 INFO | Connector openwire Started INFO | ActiveMQ JMS Message Broker (localhost, ID:dejan-bosanacs-macbook-pro.local-51917- 1265545174378- ...
Now, run the client to connect to ActiveMQ from the localhost and everything should be working fine. See the output below shows:
$ mvn exec:java -Dexec.mainClass=org.apache.activemq.book.ch3.portfolio.Publisher - Dexec.args="... Sending: {price=0.7137712112409276, stock=ORCL, offer=0.7144849824521684, up=true} on destination: Sending: {price=0.7127548328743109, stock=ORCL, offer=0.7134675877071851, up=false} on destination: Sending: {price=0.710497871629952, stock=ORCL, offer=0.711208369501582, up=false} on destination: Sending: {price=0.7167766362460622, stock=ORCL, offer=0.7174934128823083, up=true} on destination: Sending: {price=54.586310464064766, stock=CSCO, offer=54.64089677452883, up=false} on destination: Sending: {price=54.45678231194236, stock=CSCO, offer=54.5112390942543, up=false} on destination: Sending: {price=0.7134830573922482, stock=ORCL, offer=0.7141965404496403, up=false} on destination: Sending: {price=0.7125898470778729, stock=ORCL, offer=0.7133024369249507, up=false} on destination: Sending: {price=0.7106363691848542, stock=ORCL, offer=0.711347005554039, up=false} on destination: Sending: {price=54.99339386523512, stock=CSCO, offer=55.04838725910035, up=true} on destination: Published ‘10’ of ‘10’ price messages ...
If a connection attempt is made from host other than the localhost you can expect to see the following output including the exception:
$ mvn exec:java -Dexec.mainClass=org.apache.activemq.book.ch3.portfolio.Publisher - Dexec.args="... Exception in thread "main" javax.jms.JMSException: Connecting from IP address 192.168.10.10 is not allowed ...
Summary
The ActiveMQ JAAS plugins provide the ability to utilize the standardized Java login modules via simple configuration, allowing you to authenticate users from various sources, such as LDAP, properties files, and so on. Additionally, custom JAAS login modules could be created for use with other authentication or authorization schemes.
Although this example was a bit more complex, it serves as a good demonstration of the power provided by the BrokerFilter class. Just imagine how flexible this plugin mechanism is for integrating with existing custom security requirements. This example focused on a security example but many other operations that can be customized by using the pattern illustrated here.