Security notes about Hornetq as a JBoss JMS provider

I’ve been recently involved in a project where my client had to share information with some partners, using HornetQ 2.2.5 queues deployed on a JBoss 4.2.1 GA application server, and I had to face up to a security problem. Here you have the case: the first step that a remote client has to perform when it tries to send a message to a queue is to lookup for a connection factory and the queue in a JNDI tree; at this point, you should not simply open your JBoss JNDI port (1099 by default), even having configured your firewall to open the port just to your partners’ servers and having an user authentication process for JNDI, because they might list all the names of your global JNDI namespace and exploit any security bug you may have. On the other hand, although I would have opened the port, our JBoss application server runs into a demilitarized zone (DMZ) with a NAT (Network Address Translation) standard configuration, in this situation you can’t use RMI.

The solution to my problem was to force the partners to access JNDI over HTTP an secure that access in a manner that they  just could perform lookup operations over a controlled read-only context, so they got their connection factories and queues from that context and, later on, opened their JMS sessions, through a HTTPS tunnel, after an user authentication process.

In order to run a basic test case,  the first step is to create a role for the remote users (named remoteRole in this example) and another for local ones (localRole) on JBoss. On the other hand, I created one key store for the SSL connection:

keytool -genkeypair -alias test -keyalg RSA -keysize 1024 -dname OU=TEST,CN=TEST,CN=LOCAL
        -keypass test1234 -keystore hornetq.test.keystore -storepass test1234

The following task is to deploy the netty servlet that provide the HTTP transport, please follow this link where you have the WAR file I setup.  The next step is to edit the file  hornetq-configuration.xml file  of the HornetQ installation and to add a servlet connector (please, notice that the path to the test keystore is referred to the client file system and you have to provide it with the keystore) and its corresponding acceptor :

<connectors>
...  
   <connector name="netty-servlet">
      <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
      <param key="host" value="${jboss.bind.address:localhost}"/>
      <param key="port" value="8443"/>
      <param key="use-servlet" value="true"/>
      <param key="servlet-path" value="/messaging/HornetQServlet"/>
      <param key="ssl-enabled" value="true"/>
      <param key="key-store-path" value="C:\\Software\\KeyStores\\hornetq.test.keystore"/>
      <param key="key-store-password" value="test1234"/>
 </connector>
...
</connectors>

<acceptors>
..  
   <acceptor name="netty-invm">
      <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
      <param key="use-invm" value="true"/>
      <param key="host" value="org.hornetq"/>
   </acceptor>
...
</acceptors>

The following task is to be sure that the HTTPS connector of the JBoss 4.2.1 AS is running, if not you have to edit the file server.xml located in the folder \deploy\jboss-web.deployer

I also add a security constraint to this  hornetq-configuration.xml configuration file, so the remote users just can send messages to our queues:

...
<security-settings>
...
  <security-setting match="jms.queue.ReadOnly.#">
    <permission type="consume" roles="localRole"/>
    <permission type="send" roles="remoteRole"/>
  </security-setting>
...
</security-settings>
...

The following task is to edit the hornetq-jms.xml file and add a connection factory and a queue, deployed under the readonly context:

...
<connection-factory name="ReadOnly.NettyConnectionFactory">
  <xa>true</xa>
  <connectors>
    <connector-ref connector-name="netty-servlet"/>
  </connectors>
  <entries>
    <entry name="readonly/XAConnectionFactory"/>
  </entries>
</connection-factory>
...
<queue name="ReadOnly.TestQueue">
  <entry name="readonly/TestQueue"/>
</queue>
...

The next step is to code a basic messages producer, in order to be used by the remote part:

public class RemoteJMSProducer {

    public void sendMessages() {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session = null;
        Queue queue;
        MessageProducer producer;
        TextMessage message;
        Context initialContext;
        Properties jndiConfig;

        try {
            jndiConfig = new Properties();
            jndiConfig.put(Context.INITIAL_CONTEXT_FACTORY,
                           "org.jboss.naming.HttpNamingContextFactory");
            jndiConfig.put(Context.PROVIDER_URL,
                           "http://remote-jboss-server/invoker/ReadOnlyJNDIFactory");
            initialContext = new InitialContext(jndiConfig);

            connectionFactory = (ConnectionFactory)
                                 initialContext.lookup("readonly/XAConnectionFactory");
            connection = connectionFactory.createConnection("remoteUser", "test");
            queue = (Queue) initialContext.lookup("readonly/TestQueue");

            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            producer = session.createProducer(queue);
            message = session.createTextMessage();

            for (int i = 0; i < 10; i++) {
                message.setText("This is the test message number " + i);
                producer.send(message);
            }

            System.out.println("Messages sent!");
        } catch (NamingException ex) {
            System.err.println("JNDI exception: " + ex.getMessage());
        } catch (JMSException ex) {
            System.err.println("JMS exception: " + ex.getMessage());
        } finally {
            try {
                session.close();
            } catch (Exception ignore) {
            }
            try {
                connection.close();
            } catch (Exception ignore) {
            }
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        RemoteJMSProducer remoteJMSProducer;

        remoteJMSProducer = new RemoteJMSProducer();
        remoteJMSProducer.sendMessages();
    }
}

The former class doesn’t use the typical org.jnp.interfaces.NamingContextFactory, but the org.jboss.naming.HttpNamingContextFactory as the initial context factory. On the other hand, I’d like to point out that the provider URL is http://remote-jboss-server/invoker/ReadOnlyJNDIFactory. In order to run the example, you need to put the following libraries on your classpath:

  • hornetq-core-client.jar
  • hornetq-jms-client.jar
  • netty.jar
  • concurrent.jar
  • jboss-client.jar
  • jboss-common-client.jar
  • jboss-j2ee.jar
  • jboss-remoting.jar
  • jboss-serialization.jar
  • jbosssx-client.jar

Please, realize that if you try to perform a list or a rename operation, you get an exception.


References

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s