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


Weblogic: receiving messages from a remote HornetQ destination

I’ve talked in former posts about setting up a JMS bridge between Weblogic and HornetQ and how to secure that connection, but the bridge is just appropriate for sending messages from Weblogic to HornetQ, the proper way for receiving data send to HornetQ queues in Weblogic is to implement a Message Driven Bean (MDB), as you can read on the documentation of Weblogic. How to connect the MDB deployed in Weblogic to a HornetQ destination? After a research, I think that the best answer is a Foreign Server.

First of all, I copied the following HornetQ client libraries to a folder residing in a filesystem of my Weblogic server:

  • hornetq-core-client.jar
  • hornetq-jms-client.jar
  • netty.jar
  • jnp-client.jar
  • jboss-logging.jar

Later on, I set the PRE_CLASSPATH variable pointing it to these libraries into the script setDomainEnv and started Weblogic. The next step was to create a new JMS System Module on the console, accepting the defaults.  After that, I edited the module and setup a new Foreign Server with the defaults and I entered its configuration: Weblogic Foreign Server Main Configuration

The following step was to link the destinations (topics and queues) and the connection factories I previously setup on HornetQ. To do this I used the wizards provided by the Weblogic console, it’s easy, I just have to point out that I had to edit the configuration of my connection factory to add the security credentials:

Details of a Foreign Connection Factory

Finally, I mapped the queue and the connection factory in my MDB, here you have an example:

@MessageDriven(mappedName = "jms/Test/TestQueue")
@MessageDestinationConfiguration(
         connectionFactoryJNDIName = "jms/Test/TestXAConnectionFactory")
public class TestMDB implements MessageListener {
    static final Logger logger = LoggingHelper.getServerLogger();

    public void onMessage(Message message) {
        TextMessage msg = null;

        try {
            if (message instanceof TextMessage) {
                msg = (TextMessage) message;
                logger.info("MESSAGE BEAN: Message received: " + msg.getText());
            } else {
                logger.warning("Message of wrong type: " + 
                                    message.getClass().getName());
            }
        } catch (JMSException e) {
            logger.severe("TestMDB.onMessage: JMSException: " + e.toString());
            e.printStackTrace();
        } catch (Throwable te) {
            logger.severe("TestMDB.onMessage: Exception: " + te.toString());
            te.printStackTrace();
        }
    }
}

Securing a JMS bridge between Weblogic and HornetQ

I posted my notes about setting up a JMS bridge between Weblogic and HornetQ last September, now I’d like to talk about how to secure the connection: implementing an user authentication & authorization process and ensuring the communication between the servers with SSL. Finally, I’ll write about accessing to the secured queue from a Message Driven Bean (MDB) deployed on JBoss 6.1.  Most of the configuration is made on HornetQ, therefore I’ll make references to its User Manual.

The first step was to set up a security constraint, in order to ensure that just the user assigned to Weblogic 10.3 for the bridge could access to the queue of HornetQ. I had previously created a security role (testRole) and a user (testUser) belonging to it on JBoss, the procedure to do this may be different, depending on the security manager you’re using, and it’s out of the scope of this post. I used the defaults for testing purposes and just had to write a line in the files %JBOSS_HOME%\server\default\conf\props\hornetq-users.properties and %JBOSS_HOME%\server\default\conf\props\hornetq-roles.properties  Once I configured the user and the role, I edited the file %JBOSS_HOME%\ server\default\deploy\hornetq\hornetq-configuration.xml and added the following security setting:

<security-setting match="jms.queue.TestQueue">
  <permission type="consume" roles="testRole"/>
  <permission type="send" roles="testRole"/>
</security-setting>

Please, read the security chapter of the HornetQ User Manual to know about the different permissions that can be defined, addressing and wildcards.

The next step was to modify the Weblogic bridge destination on the console, in order to set up the user assigned by JBoss, here you have the excerpt from the resulting config.xml file:

<jms-bridge-destination>
  <name>JMS Bridge Destination-Target</name>
  <adapter-jndi-name>eis.jms.WLSConnectionFactoryJNDIXA</adapter-jndi-name>
  <user-name>testUser</user-name>
  <user-password-encrypted>{AES}GZTa15osrQW6/5rbE2fzm+a9BX/YPY7Hm3xiIFi0oMQ=</user-password-encrypted>
  <classpath></classpath>
  <connection-factory-jndi-name>jms/TestXAQueueConnectionFactory</connection-factory-jndi-name>
  <initial-context-factory>org.jnp.interfaces.NamingContextFactory</initial-context-factory>
  <connection-url>jnp://srvhornetq.test.local:1099</connection-url>
  <destination-jndi-name>jms/TestQueue</destination-jndi-name>
  <destination-type>Queue</destination-type>
</jms-bridge-destination>

The following issue was to secure the traffic between Weblogic and the JBoss server, where HornetQ was running as a JMS provider. To accomplish that goal, it was very useful to read the chapter about transport of the HornetQ User Manual. A previous task at this point was to prepare a self-signed certificate using keytool, the sentence I executed to create the client keystore was:

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

In order to create the trust keystore, I simply imported the client keystore into a new one:

keytool -importkeystore -srckeystore hornetq.test.keystore -destkeystore
         hornetq.test.truststore -srcstorepass test1234 -deststorepass test1234

I put these two keystores into the %JBOSS_HOME%\server\default\data\hornetq directory, so the acceptor that listens for SSL connections, could access them. I also copied the hornetq.test.keystore to the file system of Weblogic, in order to be accessed by the connector. Here you have an excerpt from the hornetq-configuration.xml file:

<connectors>
...
  <connector name="netty-ssl-connector">
    <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
    <param key="host" value="${jboss.bind.address:localhost}"/>
    <param key="port" value="${hornetq.remoting.netty.port.ssl:5446}"/>
    <param key="ssl-enabled" value="true"/>
    <param key="key-store-path" value="/oracle/hornetq/hornetq/hornetq.test.keystore"/>
    <param key="key-store-password" value="test1234"/>
  </connector>
...
</connectors>

<acceptors>
...
  <acceptor name="netty-ssl-acceptor">
    <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
    <param key="host" value="${jboss.bind.address:localhost}"/>
    <param key="port" value="${hornetq.remoting.netty.port.ssl:5446}"/>
    <param key="ssl-enabled" value="true"/>
    <param key="key-store-path" value="${jboss.server.data.dir}/hornetq/hornetq.test.keystore"/>
    <param key="key-store-password" value="test1234"/>
    <param key="trust-store-path" value="${jboss.server.data.dir}/hornetq/hornetq.test.truststore"/>
    <param key="trust-store-password" value="test1234"/>
  </acceptor>
...
</acceptors>

Finally, I deployed a MDB on JBoss that listened for the messages send from an application running on Weblogic:

@MessageDriven(name = "TestMDB", activationConfig = {
  @ActivationConfigProperty(propertyName = "acknowledgeMode",
                            propertyValue = "Auto-acknowledge"),
  @ActivationConfigProperty(propertyName = "destinationType",
                            propertyValue = "javax.jms.Queue"),
  @ActivationConfigProperty(propertyName = "destination",
                            propertyValue = "jms/TestQueue")
})
public class TestMDB implements MessageListener {
  static final Logger logger = Logger.getLogger("TestMDB");

  public void onMessage(Message message) {
    TextMessage msg = null;

    try {
      if (message instanceof TextMessage) {
        msg = (TextMessage) message;
        logger.info("MESSAGE BEAN: Message received: " + msg.getText());
      } else {
        logger.warning("Message of wrong type: " + 
                               message.getClass().getName());
      }
    } catch (JMSException e) {
      logger.severe("TestMDB.onMessage: JMSException: " + e.toString());
      e.printStackTrace();
    } catch (Throwable te) {
      logger.severe("TestMDB.onMessage: Exception: " + te.toString());
      te.printStackTrace();
    }
  }
}

But, how to access to a secured queue? The answer was to configure the following piece with the user and password into the jboss.xml file:

<enterprise-beans>
  <message-driven>
  <ejb-name>TestMDB</ejb-name>
  <mdb-user>testUser</mdb-user>
  <mdb-passwd>test1234</mdb-passwd>
  </message-driven>
</enterprise-beans>

References



Setting up a JMS bridge between Weblogic and HornetQ

I’d like to publish my notes about how to setup a basic JMS bridge between a Weblogic Server and an instance of HornetQ, running on a JBoss 6.1 Application Server. My reference was the document “Configuring and Managing the WebLogic Messaging Bridge”.

First of all, I copied the following libraries to a folder residing in a filesystem of my Weblogic server:

  • hornetq-core-client.jar
  • hornetq-jms-client.jar
  • netty.jar
  • jnp-client.jar
  • jboss-logging.jar

Later on, I set the PRE_CLASSPATH variable pointing it to these libraries into the script setDomainEnv and started Weblogic, so I could use the console to create the source bridge destination, a typical Weblogic one, just selecting the default JNDI adapter name: eis.jms.WLSConnectionFactoryJNDIXA, and putting the JNDI name of my Connection Factory and my Queue in. Here you have an excerpt of the resulting config.xml file:

<jms-bridge-destination>
   <name>JMS Bridge Destination-Source</name>
   <adapter-jndi-name>eis.jms.WLSConnectionFactoryJNDIXA</adapter-jndi-name>
   <classpath></classpath>
   <connection-factory-jndi-name>jms/TestXAQueueConnectionFactory</connection-factory-jndi-name>
   <connection-url></connection-url>
   <destination-jndi-name>jms/TestQueue</destination-jndi-name>
</jms-bridge-destination>

After that, I created the target bridge destination, the HornetQ one (I’ve previously set up the needed objects on HornetQ, please review its user manual for further information). In addition to the JNDI names of the Connection Factory and the Queue, I had to configure the URL of the JNDI server used by the HornetQ instance, in my case jnp://srvhornetq.test.local:1099. After save the destination, I had to edit it, modify the default initial connection factory weblogic.jndi.WLInitialContextFactory to org.jnp.interfaces.NamingContextFactory, and restart the Weblogic server. The resulting piece of configuration on config.xml is:

<jms-bridge-destination>
   <name>JMS Bridge Destination-Target</name>
   <adapter-jndi-name>eis.jms.WLSConnectionFactoryJNDIXA</adapter-jndi-name>
   <classpath></classpath>
   <connection-factory-jndi-name>jms/TestXAQueueConnectionFactory</connection-factory-jndi-name>
   <initial-context-factory>org.jnp.interfaces.NamingContextFactory</initial-context-factory>
   <connection-url>jnp://srvhornetq.test.local:1099</connection-url>
   <destination-jndi-name>jms/TestQueue</destination-jndi-name>
   <destination-type>Queue</destination-type>
</jms-bridge-destination>

Finally, I setup the bridge as usual, just selecting the source and target destinations, the quality of service, etc:

<messaging-bridge>
   <name>Test-Bridge</name>
   <target>DefaultServer</target>
   <source-destination>JMS Bridge Destination- Source</source-destination>
   <target-destination>JMS Bridge Destination-Target</target-destination>
   <selector></selector>
   <quality-of-service>Exactly-once</quality-of-service>
   <started>true</started>
</messaging-bridge>

Please, be careful selecting the proper parameters for each situation, in order to avoid errors like JMS-107. For example, I recommend to create a javax.jms.XATopicConnectionFactory connection factory for a topic and a javax.jms.XAQueueConnectionFactory connection factory  for a queue, but I don’t use a generic javax.jms.XAConnectionFactory Of course, the’re a lot of issues about security, transactions, etc, but that’s the topic for another post!


References