Overview
This wiki outlines the various steps required to secure a remote connection to JBossMQ using SSL.
The code, build script and configuration files are all attached at the bottom of this page
Setup
Grab a copy of a JBossAS 4.x release and create two new server configurations (based on /default) named respectively /jmsprovider and /client
Provider configuration
Step 1: Generate a private-public key pair using keytool
$ ./j2sdk1.4.2_07/bin/keytool -genkey -alias sslmq -keyalg RSA -keystore sslmq.keystore Enter keystore password: jbossmq What is your first and last name? [Unknown]: www.myhost.com What is the name of your organizational unit? [Unknown]: Some dot com What is the name of your organization? [Unknown]: Security What is the name of your City or Locality? [Unknown]: SomeCity What is the name of your State or Province? [Unknown]: Washington What is the two-letter country code for this unit? [Unknown]: US Is CN=www.myhost.com, OU=Some dot com, O=Security, L=SomeCity, ST=Washington, C=US correct? [no]: yes Enter key password for <jbossmq> (RETURN if same as keystore password):
And copy sslmq.keystore to /server/jmsprovider/conf
Step 2: Create a JAAS security domain that uses sslmq.keystore
Create a new MBean configuration ssl-domain-service.xml (see the attached example) and save it in /server/jmsprovider/deploy
<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="org.jboss.security.plugins.JaasSecurityDomain" name="jboss.security:service=JaasSecurityDomain,domain=SSL"> <constructor> <arg type="java.lang.String" value="SSL"></arg> </constructor> <attribute name="KeyStoreURL">resource:sslmq.keystore</attribute> <attribute name="KeyStorePass">jbossmq</attribute> </mbean> </server>
Step 3: Create a new UIL2 deployment descriptor ssl-uil2-service.xml and save it in /server/jmsprovider/deploy/jms
<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="org.jboss.mq.il.uil2.UILServerILService" name="jboss.mq:service=InvocationLayer,type=SSLUIL2"> <depends optional-attribute-name="Invoker">jboss.mq:service=Invoker</depends> <attribute name="ConnectionFactoryJNDIRef">SSLUIL2ConnectionFactory</attribute> <attribute name="XAConnectionFactoryJNDIRef">SSLUIL2XAConnectionFactory</attribute> <!-- The bind address --> <attribute name="BindAddress">${jboss.bind.address}</attribute> <attribute name="ServerBindPort">8193</attribute> <attribute name="PingPeriod">60000</attribute> <attribute name="EnableTcpNoDelay">true</attribute> <!-- Used to disconnect the client if there is no activity --> <!-- Ensure this is greater than the ping period --> <attribute name="ReadTimeout">70000</attribute> <!-- The size of the buffer (in bytes) wrapping the socket --> <!-- The buffer is flushed after each request --> <attribute name="BufferSize">2048</attribute> <!-- Large messages may block the ping/pong --> <!-- A pong is simulated after each chunk (in bytes) for both reading and writing --> <!-- It must be larger than the buffer size --> <attribute name="ChunkSize">1000000</attribute> <attribute name="ClientSocketFactory">org.jboss.security.ssl.ClientSocketFactory</attribute> <attribute name="ServerSocketFactory">org.jboss.security.ssl.DomainServerSocketFactory</attribute> <attribute name="SecurityDomain">java:/jaas/SSL</attribute> </mbean> </server>
Client configuration
Step 1: Create JMS client
The JMS client code is embedded in an MBean called JBossService. It has got a RW property ProviderURL that stores the IpAddress:[Port|port] of the remote provider. Its default value can be changed in jboss-service.xml or updated on the fly using the jmx console.
public void sendMessages() throws NamingException, JMSException { log.info("sending JMS message to queue bound to JNDI name: "+QUEUE_NAME); Properties properties = new Properties(); properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); properties.put(Context.URL_PKG_PREFIXES, "org.jnp.interfaces"); properties.put(Context.PROVIDER_URL, providerURL); InitialContext ctx = new InitialContext(properties); connectionFactory = (ConnectionFactory)ctx.lookup("SSLUIL2ConnectionFactory"); queue = (Destination)ctx.lookup(QUEUE_NAME); connection = connectionFactory.createConnection(); session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE); producer = session.createProducer(queue); for(int i = 0; i < NUMBER_OF_MESSAGES; i++) { Message m = session.createObjectMessage(new Integer(i)); producer.send(m); } session.commit(); log.info(NUMBER_OF_MESSAGES + " messages sent to " +providerURL); }
Notice the JNDI name of the ConnectionFactory (configured in /server/jmsprovider/ssl-uil2-service.xml)
Step 2: Create the truststore for the client
When an SSL client connects to an SSL server, it receives a certificate of authentication from the server. The client then validates the certificate against a set of certificates in its truststore.
Create the certificate $ ./j2sdk1.4.2_07/bin/keytool -export -alias sslmq -keystore sslmq.keystore -rfc -file provider.cer Enter keystore password: jbossmq Import this certificate into a client truststore $ ./j2sdk1.4.2_07/bin/keytool -import -alias sslmq -file provider.cer -keystore client.truststore Enter keystore password: jbossmq
Copy client.truststore to /server/client/conf
We now ready to set the location of the client truststore using two JVM properties; javax.net.ssl.truststore and javax.net.ssl.trustStorePassword. Since we are running 2 servers out of the same installation, we should not set them globally in run.bat or run.sh/run.conf. Instead we use the Properties service.
Edit /server/client/deploy/properties-service.xml and add the following to the 'jboss:type=Service,name=SystemProperties' MBean:
<attribute name="Properties"> javax.net.ssl.trustStore=../server/client/conf/client.truststore javax.net.ssl.trustStorePassword=jbossmq </attribute>
Note: in run.conf it would translate into
#JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStore=$JBOSS_HOME/server/client/conf/client.truststore -Djavax.net.ssl.trustStorePassword=jbossmq"
Test
Step 1: Compile and deploy
From the attachment section download the archive jmsssl.zip, explode it in the same directory where you put JBossAS then point a command line to the /build directory. Note: Make sure to change the jboss.bundle.dir property.
$ ./src/build/ant -f build.xml
In /server/client/deploy, you should have now a SAR archive named jmsssl.sar
Step 2: Have fun
If you are testing on the same pc, I recommend configuring 2 static ip addresses and binding a JBossAS instance to each one using the option --host=xxx.xxx.xxx.xxx (or -b).
So for instance to start the "provider" you would use
$ sh ./bin/run.sh -c jmsprovider -b 192.168.4.151
and this for the client
$ sh ./bin/run.sh -c client -b 192.168.4.150
To execute the client code, open the jmx console, find the service JBossService in the JMSSSL domain.
Scroll down and invoke the go() operation. In the client log, you should see the following:
21:52:07,906 INFO [JMSSSL:name=JBossService] sending JMS message to queue bound to JNDI name: queue/A 21:52:08,031 INFO [JMSSSL:name=JBossService] 5 messages sent to 192.168.4.151:1099
In another web browser, open the jmx console and check the size of Queue A (name=A,service=Queue in the jboss.mq.destination domain)
The attribute QueueDepth is now 5 (was zero previously). To see what those messages look like, you may invoke the operation listMessages located further down in the page.
Common problems
Coming soon...
I want to use SSL to "authenticate" the client?
Typically, only the client authenticates the server with SSL.
You would need your own
SSLServerSocketFactory
as JBoss's org.jboss.security.ssl.DomainServerSocketFactory does not perform or allow this configuration
Resources
Comments