JMS, JNDI and LDAPS

A recent Idea opened against MQ asked for the ability to store JMS resources using a secure connection to LDAP servers. All the current LDAP support for JMSAdmin and Explorer is documented using the plaintext protocol, but could we use a TLS-protected connection? My first thought was that this was likely to be impossible without changing something – albeit likely small – in the product code. But as I needed to get an LDAP server running locally for other reasons, I thought I’d give it a go to see if my guess was right. It wasn’t; and so here’s how you can do it yourself.

I built the simplest-possible LDAP server configuration, running in a container. The startup scripts for that image by default create a self-signed certificate based on the container id. So what I’m going to show is just sufficient to work in that environment. More complex configurations with 2-way validation of certificates and checking the signers of CA-issued certificates is feasible using the same approach; I just didn’t need that to demonstrate that a TLS-protected connection could be made. If you are working with an existing enterprise-managed LDAP server then you would get the certificates and any other details from the administrator.

The work needed is conceptually similar but different in detail for JMSAdmin and MQ Explorer.

Creating a truststore

One thing we need for TLS is a minimal truststore to validate the certificate from the LDAP server. In a production environment, you should get given any necessary files, but here I am going to create it directly. I just need the self-signed cert to be added to a store of the right format. There’s an odd use of gskit commands and openssl commands here, but that was just how the script evolved:

# Query the server to provide its self-signed certificate information
echo -n |openssl s_client -connect localhost:636 | \
   sed -ne '/---BEGIN CERTIFICATE---/,/---END CERTIFICATE---/p' > ldap.pem

# Use that PEM file to create a JKS-format file that will be used as the TrustStore
runmqckm -keydb -create -db ts.jks -type jks -pw passw0rd
runmqckm -cert  -add    -db ts.jks -file ldap.pem -pw passw0rd -label ldap

JMSAdmin

The command line interface to JNDI, JMSAdmin, essentially has 3 parts: a shell script/batch file, a configuration file, and some compiled Java classes.

We need to touch the script and the configuration, but not the class files.

JMSAdmin.config

You are always going to have to edit the supplied default configuration file. This tells how to connect to the JNDI service, and where to put object definitions. You then point to the modified configuration when running the JMSAdmin program:

JMSAdmin -cfg ./JMSAdmin.config

The only difference between a secured and non-secured connection to an LDAP server is the PROVIDER_URL. On my system, I set it to

PROVIDER_URL=ldaps://localhost:636/ou=MQ,dc=example,dc=org

Note the use of the ldaps protocol, and the portnumber in this URL. The default port for the secure protocol is 636, so it might not be a definite requirement in the URL, but I wanted to be explicit here.

JMSAdmin script

The script /opt/mqm/java/bin/JMSAdmin is a short program that sets up the environment and path to a JRE and the MQ classes that do the real work. I copied this file to my own directory and made a small change, to be able to pass through additional parameters when running java.

$ diff JMSAdmin /opt/mqm/java/bin/JMSAdmin
65c65
<         $AMQJAVA $MQ_EXTRA_DEFS -classpath $MQ_JAVA_INSTALL_PATH/lib/com.ibm.mq.allclient.jar -Dcom.ibm.msg.client.commonservices.log.outputName=$MQ_JAVA_DATA_PATH/log -Dcom.ibm.msg.client.commonservices.trace.outputName=$MQ_JAVA_DATA_PATH/trace -DMQ_JAVA_INSTALL_PATH=$MQ_JAVA_INSTALL_PATH com.ibm.mq.jms.admin.JMSAdmin $*
---
>         $AMQJAVA -classpath $MQ_JAVA_INSTALL_PATH/lib/com.ibm.mq.allclient.jar -Dcom.ibm.msg.client.commonservices.log.outputName=$MQ_JAVA_DATA_PATH/log -Dcom.ibm.msg.client.commonservices.trace.outputName=$MQ_JAVA_DATA_PATH/trace -DMQ_JAVA_INSTALL_PATH=$MQ_JAVA_INSTALL_PATH com.ibm.mq.jms.admin.JMSAdmin $*
67c67
<         $AMQJAVA $MQ_EXTRA_DEFS -Dcom.ibm.msg.client.commonservices.log.outputName=$MQ_JAVA_DATA_PATH/log -Dcom.ibm.msg.client.commonservices.trace.outputName=$MQ_JAVA_DATA_PATH/trace -DMQ_JAVA_INSTALL_PATH=$MQ_JAVA_INSTALL_PATH com.ibm.mq.jms.admin.JMSAdmin $*
---
>         $AMQJAVA -Dcom.ibm.msg.client.commonservices.log.outputName=$MQ_JAVA_DATA_PATH/log -Dcom.ibm.msg.client.commonservices.trace.outputName=$MQ_JAVA_DATA_PATH/trace -DMQ_JAVA_INSTALL_PATH=$MQ_JAVA_INSTALL_PATH com.ibm.mq.jms.admin.JMSAdmin $*

If we set the MQ_EXTRA_DEFS environment variable, then it is passed through. If we do not set it, the behaviour doesn’t change.

I then created a wrapper script to call this modified program. I could have merged this script with the new JMSAdmin, but it was easier this way when experimenting.

$ cat JWrap.sh

. setmqenv -m QM1 -k

MQ_EXTRA_DEFS=""
MQ_EXTRA_DEFS="$MQ_EXTRA_DEFS -Djavax.net.ssl.trustStore=./ts.jks"
MQ_EXTRA_DEFS="$MQ_EXTRA_DEFS -Djavax.net.ssl.trustStorePassword=passw0rd"

# See https://www.ibm.com/support/pages/websphere-endpoint-identification-enabled-ldaps-connections for the meaning of this one
MQ_EXTRA_DEFS="$MQ_EXTRA_DEFS -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true"

# MQ_EXTRA_DEFS="$MQ_EXTRA_DEFS -Djavax.net.debug=true"

export MQ_EXTRA_DEFS

./JMSAdmin -cfg ./JMSAdmin.config 

The important bit here is setting the trustStore and its password. Being able to use debug on the JSSE connection by setting javax.net.debug showed immediately that TLS was, as hoped, in use. It also assisted with resolving any problems with certificates. And because the certificate includes a weird hostname related to its container id, I needed to bypass the check that it matches a real hostname – again, you would not need that in a properly-managed system. For two-way authentication to the server, you would likely also need definitions of javax.net.ssl.keyStore and password.

And that was all. Running JWrap.shbrings me to a command input to define or display the JNDI resources.

$ JWrap.sh
Licensed Materials - Property of IBM
5724-H72, 5655-R36, 5724-L26, 5655-L82
(c) Copyright IBM Corp. 2008, 2023 All Rights Reserved.
US Government Users Restricted Rights - Use, duplication or
disclosure restricted by GSA ADP Schedule Contract with
IBM Corp.
Starting IBM MQ classes for Java(tm) Message Service Administration

InitCtx> dis q(*)
Q(cn=Q1)
    CCSID(1208)
    ENCODING(NATIVE)
   ...

MQ Explorer

This one also turned out to be possible, but was a bit trickier.

MQExplorer.ini

The system properties are settable in the same kind of way by editing the configuration file used when Explorer starts. On my system, I’ve installed Explorer into the /opt/MQExplorer tree and that contains the startup ini file. Edit it:

$ sudo vi /opt/MQExplorer/MQExplorer.ini

and put the definitions for access to the truststore in there after the -vmargssection:

-vm
jre/jre/bin
-vmargs
-Xmx512M
-Djavax.net.ssl.trustStore=/home/metaylor/mf/ldap/server/ts.jks
-Djavax.net.ssl.trustStorePassword=passw0rd
-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true

The only thing you might need to watch for here is that the trustStore (and keyStore if needed) definitions are going to be global to the JRE. So that might affect other activity in the Explorer – client connections to managed queue managers have their own definitions for these stores that should not be affected, but you would probably want to verify that.

Initial Contexts

Managing the equivalent of the PROVIDER_URL field is a little harder. The pre-defined options build the URL from input parameters, but do not give a way to switch to the ldaps protocol element from the LDAP option.

Instead, you can use the “Other” option, which asks for the factory class (use the same as for the regular LDAP selection) and a directory (use the directory where the Explorer’s JRE jar files live). This variation allows you to type in the full URL including the secure protocol.

Adding an LDAPS context to Explorer
Adding an LDAPS context to Explorer

Conclusion

Using LDAPS for JNDI resources is possible using these extensions, without needing any changes to the MQ product itself. There are things that might make it easier, particularly in the Explorer GUI, and we will consider changes, but it’s not absolutely essential.

I hope this helps people wanting to secure connections to their LDAP servers when defining JMS objects.

Change History:

  • 2023/02/02: Initial version
  • 2023/02/03: Rewrite the Explorer section to use the “other” option for contexts

This post was last updated on February 3rd, 2023 at 08:31 am

Leave a Reply

Your email address will not be published. Required fields are marked *