diff --git a/README.md b/README.md index 25f4e79..9f2f014 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ components/* | Contains reusable components Path | Description ---|--- output/env.json | Used for setting environmental variables such as username and password -output/com/ibm/mq/samples/jms/PubSubBase.java | The base used for generated publishers and subscribers +output/com/asyncapi/PubSubBase.java | The base used for generated publishers and subscribers ## Container Information diff --git a/components/Common.js b/components/Common.js index e0cef74..2649a72 100644 --- a/components/Common.js +++ b/components/Common.js @@ -16,6 +16,7 @@ import { createJavaArgsFromProperties } from '../utils/Types.utils'; import { collateModelNames } from '../utils/Models.utils'; +import { MQCipherToJava } from './Connection/MQTLS'; export function Class({ childrenContent, name, implementsClass, extendsClass }) { if (childrenContent === undefined) { @@ -124,7 +125,15 @@ export function EnvJson({ asyncapi, params }) { const mqChannel = getMqValues(url,'mqChannel'); const host = URLtoHost(url); const domain = host.split(':', 1); - + let cipher = protocol === 'ibmmq-secure' ? 'ANY' : ''; + + if ( + protocol === 'ibmmq-secure' && + asyncapi.server(params.server).bindings().ibmmq.cipherSpec + ) { + cipher = MQCipherToJava(asyncapi.server(params.server).bindings().ibmmq.cipherSpec); + } + return ` { "MQ_ENDPOINTS": [{ @@ -133,7 +142,8 @@ export function EnvJson({ asyncapi, params }) { "CHANNEL": "${mqChannel}", "QMGR": "${qmgr}", "APP_USER": "${user}", - "APP_PASSWORD": "${password}" + "APP_PASSWORD": "${password}", + "CIPHER_SUITE": "${cipher}" }] } `; diff --git a/components/Connection/MQTLS.js b/components/Connection/MQTLS.js new file mode 100644 index 0000000..62b846a --- /dev/null +++ b/components/Connection/MQTLS.js @@ -0,0 +1,45 @@ +export function MQCipherToJava(cipher) { + // List in line with Oracle JRE mappings from https://ibm.biz/mq-cipher-mappings + const ciphers = { + ECDHE_ECDSA_3DES_EDE_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + ECDHE_ECDSA_AES_128_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + ECDHE_ECDSA_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + ECDHE_ECDSA_AES_256_CBC_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + ECDHE_ECDSA_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + ECDHE_ECDSA_NULL_SHA256: "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + ECDHE_ECDSA_RC4_128_SHA256: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + ECDHE_RSA_3DES_EDE_CBC_SHA256: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + ECDHE_RSA_AES_128_CBC_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + ECDHE_RSA_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + ECDHE_RSA_AES_256_CBC_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + ECDHE_RSA_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + ECDHE_RSA_NULL_SHA256: "TLS_ECDHE_RSA_WITH_NULL_SHA", + ECDHE_RSA_RC4_128_SHA256: "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA", + TLS_RSA_WITH_AES_128_CBC_SHA256: "TLS_RSA_WITH_AES_128_CBC_SHA256", + TLS_RSA_WITH_AES_128_GCM_SHA256: "TLS_RSA_WITH_AES_128_GCM_SHA256", + TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA", + TLS_RSA_WITH_AES_256_CBC_SHA256: "TLS_RSA_WITH_AES_256_CBC_SHA256", + TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384", + TLS_RSA_WITH_DES_CBC_SHA: "SSL_RSA_WITH_DES_CBC_SHA", + TLS_RSA_WITH_NULL_SHA256: "TLS_RSA_WITH_NULL_SHA256", + TLS_RSA_WITH_RC4_128_SHA256: "SSL_RSA_WITH_RC4_128_SHA", + ANY_TLS12: "*TLS12", + TLS_AES_128_GCM_SHA256: "TLS_AES_128_GCM_SHA256", + TLS_AES_256_GCM_SHA384: "TLS_AES_256_GCM_SHA384", + TLS_CHACHA20_POLY1305_SHA256: "TLS_CHACHA20_POLY1305_SHA256", + TLS_AES_128_CCM_SHA256: "TLS_AES_128_CCM_SHA256", + TLS_AES_128_CCM_8_SHA256: "TLS_AES_128_CCM_8_SHA256", + ANY: "*ANY", + ANY_TLS13: "*TLS13", + ANY_TLS12_OR_HIGHER: "*TLS12ORHIGHER", + ANY_TLS13_OR_HIGHER: "*TLS13ORHIGHER" + } + + if (ciphers[cipher] === undefined) { + throw new Error('An invalid cipher spec was provided. Please see https://ibm.biz/mq-cipher-mappings'); + } + + return ciphers[cipher]; + } \ No newline at end of file diff --git a/components/PubSubBase/MQPubSubBase.js b/components/PubSubBase/MQPubSubBase.js index 108d2cc..b6c2aa9 100644 --- a/components/PubSubBase/MQPubSubBase.js +++ b/components/PubSubBase/MQPubSubBase.js @@ -77,7 +77,7 @@ function getPubSubContent(params) { MQFirst.get("APP_PASSWORD").toString(), null, topicName, - null); + MQFirst.get("CIPHER_SUITE").toString()); // Build connection helper ch = new ConnectionHelper(id, myConnection); diff --git a/package.json b/package.json index f4e4964..5c017c4 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ }, "supportedProtocols": [ "ibmmq", + "ibmmq-secure", "kafka", "kafka-secure" ], diff --git a/test/Common.test.js b/test/Common.test.js index 7936524..f707fe2 100644 --- a/test/Common.test.js +++ b/test/Common.test.js @@ -160,7 +160,8 @@ test('EnvJson extracts correct values', async () => { CHANNEL: 'DEV.APP.SVRCONN', QMGR: 'QM1', APP_USER: 'app', - APP_PASSWORD: 'passw0rd' + APP_PASSWORD: 'passw0rd', + CIPHER_SUITE: '*TLS12' }] }); expect(generatedJson).toBe(expectedJson); diff --git a/test/mocks/single-channel.yml b/test/mocks/single-channel.yml index b7e5eaa..7c161a2 100644 --- a/test/mocks/single-channel.yml +++ b/test/mocks/single-channel.yml @@ -6,8 +6,11 @@ info: servers: production: url: ibmmq://localhost:1414/QM1/DEV.APP.SVRCONN - protocol: ibmmq + protocol: ibmmq-secure description: Production Instance 1 + bindings: + ibmmq: + cipherSpec: ANY_TLS12 channels: song/released: publish: diff --git a/tutorials/IBMMQ.md b/tutorials/IBMMQ.md index b6e9f59..01d68c0 100644 --- a/tutorials/IBMMQ.md +++ b/tutorials/IBMMQ.md @@ -28,6 +28,16 @@ You will then need to enter the directory you have just created, for example wit cd ~/asyncapi-java-tutorial ``` +## TLS + +If you are connecting to servers using the ibmmq-secure binding and have a `cipherSpec` defined in your service specification, you will need to generate certificates and configure a keystore before your generated application will function. Please see [secure communication between IBM MQ endpoints with TLS](https://developer.ibm.com/tutorials/mq-secure-msgs-tls/) for more details. + +With a keystore configured, the commands for steps 5 and 6 of the next section will need to include details of this, in the following format: + +``` +java -cp target/asyncapi-java-generator-0.1.0.jar -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.trustStore=clientkey.jks -Djavax.net.ssl.trustStorePassword=password -Dcom.ibm.mq.cfg.useIBMCipherMappings=false com.asyncapi.DemoSubscriber +``` + ## Running the Publisher/Subscriber Template These commands will allow you to run the Java Template publisher/subscriber model using IBM MQ. 1. Run the AsyncAPI Generator.
**Note:** You may need to change the username and password values if you have not followed the IBM MQ tutorial. @@ -60,6 +70,7 @@ These commands will allow you to run the Java Template publisher/subscriber mode java -cp target/asyncapi-java-generator-0.1.0.jar com.asyncapi.DemoProducer ``` + The messages will now be seen to be being sent from the running publisher to the running subscriber, using MQ topics. Your output from your subscriber should look something like ``` Oct 14, 2021 9:53:23 AM com.asyncapi.SingleReleasedSubscriber receive