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
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.
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
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.
/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
$ 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) ...
This one also turned out to be possible, but was a bit trickier.
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
-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.
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.
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.
- 2023/02/02: Initial version
- 2023/02/03: Rewrite the Explorer section to use the “other” option for contexts