Even with all security disabled, an MQ client cannot access a queue manager on CP4I without at least one-way TLS.
This example shows how to set up one-way TLS and deploy a queue manager to OpenShift. To test, we use the MQ sample clients amqsputc
and amqsgetc
to put and get messages from a queue.
Source: This is based on https://www.ibm.com/docs/en/ibm-mq/9.3?topic=manager-example-configuring-tls
Open a terminal and login to the OpenShift cluster where you installed the CP4I MQ Operator.
Clone this repository and navigate to this directory:
git clone https://github.com/ibm-messaging/cp4i-mq-samples.git
cd cp4i-mq-samples/01-tls
Delete the files and OpenShift resources created by this example:
./cleanup-qm1.sh
You can copy/paste the commands shown here, or run the script deploy-qm1-qmgr.sh.
Remember you must be logged in to your OpenShift cluster.
openssl req -newkey rsa:2048 -nodes -keyout qm1.key -subj "/CN=qm1" -x509 -days 3650 -out qm1.crt
This creates two files:
-
Private key:
qm1.key
-
Certificate:
qm1.crt
Check:
ls qm1.*
You should see:
qm1.crt qm1.key
You can also inspect the certificate:
openssl x509 -text -noout -in qm1.crt
You'll see (truncated for redability):
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 13882868190759648755 (0xc0a9db109dcc7df3)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=qm1
Validity
Not Before: Jul 21 09:15:33 2021 GMT
Not After : Jul 19 09:15:33 2031 GMT
Subject: CN=qm1
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
...
Note this is a self-signed certificate (Issuer is the same as Subject).
The client key database will contain the queue manager certificate, so the client can verify the certificate that the queue manager sends during the TLS handshake.
runmqakm -keydb -create -db app1key.kdb -pw password -type cms -stash
This creates 4 files:
-
Key database:
app1key.kdb
-
Revocation list:
app1key.crl
-
Certificate requests:
app1key.rdb
-
Password stash:
app1key.sth
. Used to pass the password ("password"
) in commands instead of promting the user.
runmqakm -cert -add -db app1key.kdb -label qm1cert -file qm1.crt -format ascii -stashed
To check, list the database certificates:
runmqakm -cert -list -db app1key.kdb -stashed
Certificates found
* default, - personal, ! trusted, # secret key
! qm1cert
You can also get certificate details:
runmqakm -cert -details -db app1key.kdb -stashed -label qm1cert
We create a kubernetes secret with the queue manager's certificate and private key. The secret will be used, when creating the queue manager, to populate the queue manager's key database.
oc create secret tls example-01-qm1-secret -n cp4i --key="qm1.key" --cert="qm1.crt"
cat > qm1-configmap.yaml << EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: example-01-qm1-configmap
data:
qm1.mqsc: |
DEFINE QLOCAL('Q1') REPLACE DEFPSIST(YES)
DEFINE CHANNEL(QM1CHL) CHLTYPE(SVRCONN) REPLACE TRPTYPE(TCP) SSLCAUTH(OPTIONAL) SSLCIPH('ANY_TLS12_OR_HIGHER')
SET CHLAUTH(QM1CHL) TYPE(BLOCKUSER) USERLIST('nobody') ACTION(ADD)
EOF
#
cat qm1-configmap.yaml
The MQSC statements above will run when the queue manager is created:
-
Create a local queue called
Q1
. When testing, clients will put to and get from this queue. -
Create a Server Connection channel called
QM1CHL
with a TLS cipherspec (ANY_TLS12_OR_HIGHER
) and optional TLS client authentication (SSLCAUTH(OPTIONAL)
).
SSLCAUTH(OPTIONAL)
makes the TLS connection one-way: the queue manager must send its certificate but the client doesn't have to.
- A Channel Authentication record that allows clients to connect to
QM1CHL
("block nobody" reverses the CHLAUTH setting that blocks channels connections by default).
oc apply -n cp4i -f qm1-configmap.yaml
MQ Clients use Server Name Indication (SNI) to connect to queue managers on OpenShift. This requires a route with a host name in the form <lowercase channel name>.chl.mq.ibm.com
. We create that route below.
It is easier to only use uppercase letters and numbers for the channel name. This makes the route's host name easier to determine. Other characters are converted according to rules described here.
For example:
-
Channel
TO.QMGR1
maps to an SNI address ofto2e-qmgr1.chl.mq.ibm.com
. -
Channel
to.qmgr1
maps to an SNI address of74-6f-2e-71-6d-67-72-1.chl.mq.ibm.com
. -
Channel
QM1CHL
maps to an SNI address ofqm1chl.chl.mq.ibm.com
. Much more readable!
cat > qm1chl-route.yaml << EOF
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: example-01-qm1-route
spec:
host: qm1chl.chl.mq.ibm.com
to:
kind: Service
name: qm1-ibm-mq
port:
targetPort: 1414
tls:
termination: passthrough
EOF
#
cat qm1chl-route.yaml
oc apply -n cp4i -f qm1chl-route.yaml
Check:
oc describe route example-01-qm1-route
You should see:
Name: example-01-qm1-route
Namespace: cp4i
Created: 15 seconds ago
Labels: <none>
Annotations: ...
Requested Host: qm1chl.chl.mq.ibm.com
exposed on router default (host ...
Path: <none>
TLS Termination: passthrough
Insecure Policy: <none>
Endpoint Port: 1414
Service: qm1-ibm-mq
Weight: 100 (100%)
Endpoints: <error: endpoints "qm1-ibm-mq" not found>
Note the Endpoints
error at the end. This is because the route points to a service (the queue manager's) that does not exist yet. It will be created with the queue manager.
cat > qm1-qmgr.yaml << EOF
apiVersion: mq.ibm.com/v1beta1
kind: QueueManager
metadata:
name: qm1
spec:
license:
accept: true
license: L-RJON-CD3JKX
use: NonProduction
queueManager:
name: QM1
mqsc:
- configMap:
name: example-01-qm1-configmap
items:
- qm1.mqsc
storage:
queueManager:
type: ephemeral
template:
pod:
containers:
- env:
- name: MQSNOAUT
value: 'yes'
name: qmgr
version: 9.3.0.0-r2
web:
enabled: false
pki:
keys:
- name: example
secret:
secretName: example-01-qm1-secret
items:
- tls.key
- tls.crt
EOF
#
cat qm1-qmgr.yaml
- Version:
version: 9.3.0.0-r2
The MQ version depends on the OpenShift MQ Operator version. To find out your MQ Operator version:
oc get sub -n cp4i
In this case, the result is (formatted for readbility; your output may differ):
NAME PACKAGE SOURCE CHANNEL
...
ibm-mq ibm-mq ibm-operator-catalog v2.0
See Version support for the IBM MQ Operator for a list of MQ versions supported by this MQ Operator version.
- License:
license:
accept: true
license: L-RJON-CD3JKX
use: NonProduction
The license is correct for the MQ version. If you are installing a different MQ version, you'll find the correct license in Licensing reference for mq.ibm.com/v1beta1.
- MQSC statements:
mqsc:
- configMap:
name: example-01-qm1-configmap
items:
- qm1.mqsc
The above points to the configmap with MQSC statements we created earlier. The MQSC statements will run when the queue manager is deployed.
- No user authentication:
- env:
- name: MQSNOAUT
value: 'yes'
Setting the environment variable MQSNOAUT=yes
disables user authentication (clients don't have to provide userid and password when connecting, and user authority to access resources is not checked). In CP4I, non-production queue managers have this setting by default.
- MQ Web Console:
web:
enabled: false
In Cloud Pak for Integration, the MQ Web Console is accessed from Platform Navigator. We are using the minimum configuration needed to run a queue manager, so are setting this to enabled: false
. If you enable this setting to use the MQ Web Console, you must install the Cloud Pak for Integration operator and create an instance of Platform Navigator.
- Queue manager key and certificate:
pki:
keys:
- name: example
secret:
secretName: example-01-qm1-secret
items:
- tls.key
- tls.crt
The pki
section points to the secret (created earlier) containing the queue manager's certificate and private key.
oc apply -n cp4i -f qm1-qmgr.yaml
We will put, browse and get messages to test the queue manager we just deployed.
You can copy/paste the commands shown below to a command line, or use these scripts:
- run-qm1-client-put.sh to put two test messages to the queue
Q1
. - run-qm1-client-browse.sh to browse the messages (read them but leave them on the queue).
- run-qm1-client-get.sh to get messages (read them and remove them from the queue).
It takes 2-5 minutes for the queue manager state to go from "Pending" to "Running".
oc get qmgr -n cp4i qm1
The client needs this to specify the host to connec to.
qmhostname=`oc get route -n cp4i qm1-ibm-mq-qm -o jsonpath="{.spec.host}"`
echo $qmhostname
Test (optional):
ping -c 3 $qmhostname
The CCDT tells the client where the queue manager is (host and port), the channel name, and the TLS cipher (encryption and signing algorithms) to use.
For details, see Configuring a JSON format CCDT.
cat > ccdt.json << EOF
{
"channel":
[
{
"name": "QM1CHL",
"clientConnection":
{
"connection":
[
{
"host": "$qmhostname",
"port": 443
}
],
"queueManager": "QM1"
},
"transmissionSecurity":
{
"cipherSpecification": "ANY_TLS12_OR_HIGHER"
},
"type": "clientConnection"
}
]
}
EOF
#
cat ccdt.json
"transmissionSecurity":
{
"cipherSpecification": "ANY_TLS12_OR_HIGHER"
},
The above enables TLS on the connection. It sets a cipher specification (ANY_TLS12_OR_HIGHER
) that negotiates the highest level of security that the remote end will allow but will only connect using a TLS 1.2 or higher protocol. For details, see Enabling CipherSpecs.
We set two environment variables:
-
MQCCDTURL
points toccdt.json
. -
MQSSLKEYR
points to the key database. Note this must be the file name without the.kdb
extension.
export MQCCDTURL=ccdt.json
export MQSSLKEYR=app1key
# check:
echo MQCCDTURL=$MQCCDTURL
ls -l $MQCCDTURL
echo MQSSLKEYR=$MQSSLKEYR
ls -l $MQSSLKEYR.*
For MQCCDTURL
, we use the simplest form that works in this situation (the CCDT is in the directory where the clients run). Other valid forms are:
- Full path:
export MQCCDTURL=`pwd`/ccdt.json
- Full path (resolving symlinks):
thisDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
export MQCCDTURL="${thisDir}/ccdt.json"
- File URL format
export MQCCDTURL=file://`pwd`/ccdt.json
Same for MQSSLKEYR
. It is also possible to use the full path to the key database:
export MQSSLKEYR=`pwd`/app1key
echo "Test message 1" | amqsputc Q1 QM1
echo "Test message 2" | amqsputc Q1 QM1
You should see:
Sample AMQSPUT0 start
target queue is Q1
Sample AMQSPUT0 end
Sample AMQSPUT0 start
target queue is Q1
Sample AMQSPUT0 end
The program gets and displays the messages and waits for more. Ends after 15 seconds if no more messages:
amqsgetc Q1 QM1
You should see:
Sample AMQSGET0 start
message <Test message 1>
message <Test message 2>
no more messages
Sample AMQSGET0 end
This deletes the queue manager and other objects created on OpenShift, and the files created by this example:
./cleanup-qm1.sh
Next we'll try to implement mutual TLS. See 02-mtls.