Skip to content

Commit 81dff84

Browse files
authored
Merge pull request #124 from Cinimex-Informatica/feature/issue119_tls_support
add TLS support
2 parents d0f7f82 + 92997a9 commit 81dff84

File tree

8 files changed

+181
-52
lines changed

8 files changed

+181
-52
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,21 @@ qmgrConnectionParams:
8989
# How long to wait until metrics are published by queue manager (milliseconds).
9090
# Value must be at least 10000 (periodicity with which metrics are published by MQ).
9191
connTimeout: 12000
92-
92+
# Use TLS connection to queue manager? If useTLS equals "false" than all connection parameters below will be ignored.
93+
useTLS: true
94+
# Path to keystore file
95+
keystorePath: /opt/mq_exporter/keystores/keystore.jks
96+
# Keystore password
97+
keystorePassword: testpass2
98+
# Path to truststore file
99+
truststorePath: /opt/mq_exporter/keystores/truststore.jks
100+
# Truststore password
101+
truststorePassword: testpass2
102+
# SSL protocol
103+
sslProtocol: TLSv1.2
104+
# CipherSuite
105+
cipherSuite: TLS_RSA_WITH_AES_256_CBC_SHA256
106+
93107
# Prometheus connection information -------------------------------
94108
prometheusEndpointParams:
95109
# URL and port which will be used to expose metrics for Prometheus.

src/main/java/ru/cinimex/exporter/Config.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.apache.logging.log4j.LogManager;
44
import org.apache.logging.log4j.Logger;
55
import org.yaml.snakeyaml.Yaml;
6+
import ru.cinimex.exporter.mq.MQSecurityProperties;
67

78
import java.io.BufferedReader;
89
import java.io.File;
@@ -34,6 +35,7 @@ public class Config {
3435
private boolean sendPCFCommands;
3536
private boolean usePCFWildcards;
3637
private int scrapeInterval;
38+
private MQSecurityProperties mqSecurityProperties;
3739

3840
public Config(String path) {
3941
Yaml file = new Yaml();
@@ -65,6 +67,21 @@ public Config(String path) {
6567
this.sendPCFCommands = (boolean) pcfParameters.get("sendPCFCommands");
6668
this.usePCFWildcards = (boolean) pcfParameters.get("usePCFWildcards");
6769
this.scrapeInterval = (Integer) pcfParameters.get("scrapeInterval");
70+
boolean useTLS = (boolean) qmgrConnectionParams.get("useTLS");
71+
if (useTLS) {
72+
logger.debug("Secured connection to queue manager will be used");
73+
mqSecurityProperties = new MQSecurityProperties(
74+
useTLS,
75+
(String) qmgrConnectionParams.get("keystorePath"),
76+
(String) qmgrConnectionParams.get("keystorePassword"),
77+
(String) qmgrConnectionParams.get("truststorePath"),
78+
(String) qmgrConnectionParams.get("truststorePassword"),
79+
(String) qmgrConnectionParams.get("sslProtocol"),
80+
(String) qmgrConnectionParams.get("cipherSuite")
81+
);
82+
} else {
83+
logger.debug("Unsecured connection to queue manager will be used");
84+
}
6885
logger.info("Successfully parsed configuration file!");
6986
}
7087

@@ -132,4 +149,7 @@ public List<String> getQueues() {
132149
return queues;
133150
}
134151

152+
public MQSecurityProperties getMqSecurityProperties() {
153+
return mqSecurityProperties;
154+
}
135155
}

src/main/java/ru/cinimex/exporter/ExporterLauncher.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public static void main(String[] args) {
6363
}
6464

6565
MetricsManager.initMetrics(elements, monitoringTypes);
66-
MQSubscriberManager manager = new MQSubscriberManager(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp());
66+
MQSubscriberManager manager = new MQSubscriberManager(config);
6767
manager.runSubscribers(elements, objects, config.sendPCFCommands(), config.usePCFWildcards(),
6868
config.getScrapeInterval(), config.getConnTimeout());
6969
try {
@@ -87,7 +87,7 @@ private static ArrayList<PCFElement> getAllPublishedMetrics(Config config) {
8787
gmo.options = GMO;
8888
gmo.waitInterval = 30000;
8989
try {
90-
connection.establish(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp());
90+
connection.establish(config.getQmgrName(), MQConnection.createMQConnectionParams(config));
9191
topic = connection.createTopic(String.format(TOPIC_STRING, config.getQmgrName()));
9292
MQMessage msg = getEmptyMessage();
9393
topic.get(msg, gmo);
@@ -107,7 +107,8 @@ private static ArrayList<PCFElement> getAllPublishedMetrics(Config config) {
107107
elements.addAll(PCFDataParser.getPCFElements(pcfResponse));
108108
}
109109
}
110-
} catch (MQException | IOException e) {
110+
} catch (MQException |
111+
IOException e) {
111112
logger.error("Failed!", e);
112113
} finally {
113114
try {

src/main/java/ru/cinimex/exporter/mq/MQConnection.java

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,23 @@
77
import com.ibm.mq.constants.MQConstants;
88
import org.apache.logging.log4j.LogManager;
99
import org.apache.logging.log4j.Logger;
10+
import ru.cinimex.exporter.Config;
1011

12+
import javax.net.ssl.KeyManagerFactory;
13+
import javax.net.ssl.SSLContext;
14+
import javax.net.ssl.SSLSocketFactory;
15+
import javax.net.ssl.TrustManagerFactory;
16+
import java.io.FileInputStream;
17+
import java.io.IOException;
18+
import java.security.KeyManagementException;
19+
import java.security.KeyStore;
20+
import java.security.KeyStoreException;
21+
import java.security.NoSuchAlgorithmException;
22+
import java.security.UnrecoverableKeyException;
23+
import java.security.cert.CertificateException;
24+
import java.util.HashMap;
1125
import java.util.Hashtable;
26+
import java.util.Map;
1227

1328
/**
1429
* Class represents MQ connection.
@@ -20,52 +35,71 @@ public class MQConnection {
2035
/**
2136
* Method creates connection properties Hashtable from connection parameters.
2237
*
23-
* @param host - host, where queue manager is located.
24-
* @param port - queue manager's port.
25-
* @param channel - queue manager's channel.
26-
* @param user - user, which has enough privilege on the queue manager (optional).
27-
* @param password - password, which is required to establish connection with queue manager (optional).
28-
* @param useMQCSP - flag, which indicates, if MQCSP auth should be used.
38+
* @param config - object containing different properties.
2939
* @return - returns prepared structure with all parameters transformed into queue manager's format.
3040
*/
31-
protected static Hashtable<String, Object> createMQConnectionParams(String host, int port, String channel, String user, String password, boolean useMQCSP) {
32-
Hashtable<String, Object> properties = new Hashtable<>();
33-
properties.put(MQConstants.TRANSPORT_PROPERTY, host == null ? MQConstants.TRANSPORT_MQSERIES_BINDINGS : MQConstants.TRANSPORT_MQSERIES_CLIENT);
34-
if (host != null) properties.put(MQConstants.HOST_NAME_PROPERTY, host);
35-
if (port != 0) properties.put(MQConstants.PORT_PROPERTY, port);
36-
if (channel != null) properties.put(MQConstants.CHANNEL_PROPERTY, channel);
37-
if (user != null || password != null) {
38-
if (useMQCSP) properties.put(MQConstants.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
39-
if (user != null) properties.put(MQConstants.USER_ID_PROPERTY, user);
40-
if (password != null) properties.put(MQConstants.PASSWORD_PROPERTY, password);
41+
public static Map<String, Object> createMQConnectionParams(Config config) {
42+
Map<String, Object> properties = new HashMap<>();
43+
properties.put(MQConstants.TRANSPORT_PROPERTY, config.getQmgrHost() == null ? MQConstants.TRANSPORT_MQSERIES_BINDINGS : MQConstants.TRANSPORT_MQSERIES_CLIENT);
44+
if (config.getQmgrHost() != null) properties.put(MQConstants.HOST_NAME_PROPERTY, config.getQmgrHost());
45+
if (config.getQmgrPort() != 0) properties.put(MQConstants.PORT_PROPERTY, config.getQmgrPort());
46+
if (config.getQmgrChannel() != null) properties.put(MQConstants.CHANNEL_PROPERTY, config.getQmgrChannel());
47+
if (config.getUser() != null || config.getPassword() != null) {
48+
if (config.useMqscp()) properties.put(MQConstants.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
49+
if (config.getUser() != null) properties.put(MQConstants.USER_ID_PROPERTY, config.getUser());
50+
if (config.getPassword() != null) properties.put(MQConstants.PASSWORD_PROPERTY, config.getPassword());
51+
}
52+
MQSecurityProperties mqSecurityProperties = config.getMqSecurityProperties();
53+
if (mqSecurityProperties != null && mqSecurityProperties.isUseTLS()) {
54+
properties.put(MQConstants.SSL_CIPHER_SUITE_PROPERTY, mqSecurityProperties.getCipherSuite());
55+
properties.put(MQConstants.SSL_SOCKET_FACTORY_PROPERTY, getSslSocketFactory(mqSecurityProperties));
56+
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
4157
}
4258
return properties;
4359
}
4460

4561
/**
46-
* Method establishes connection with queue manager.
62+
* Method creates SSLSocketFactory from connection parameters.
4763
*
48-
* @param host - host, where queue manager is located.
49-
* @param port - queue manager's port.
50-
* @param channel - queue manager's channel.
51-
* @param qmName - queue manager's name.
52-
* @param user - user, which has enough privilege on the queue manager (optional).
53-
* @param password - password, which is required to establish connection with queue manager (optional).
54-
* @param useMQCSP - flag, which indicates, if MQCSP auth should be used.
64+
* @param mqSecurityProperties - object containing security properties.
65+
* @return - returns prepared SSLSocketFactory.
5566
*/
56-
public void establish(String host, int port, String channel, String qmName, String user, String password, boolean useMQCSP) throws MQException {
57-
Hashtable<String, Object> connectionProperties = createMQConnectionParams(host, port, channel, user, password, useMQCSP);
58-
queueManager = new MQQueueManager(qmName, connectionProperties);
67+
private static SSLSocketFactory getSslSocketFactory(MQSecurityProperties mqSecurityProperties) {
68+
KeyStore keyStore = getStore(mqSecurityProperties.getKeystorePath(), mqSecurityProperties.getKeystorePassword());
69+
KeyStore trustStore = getStore(mqSecurityProperties.getTruststorePath(), mqSecurityProperties.getTruststorePassword());
70+
SSLContext sslContext = null;
71+
try {
72+
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
73+
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
74+
trustManagerFactory.init(trustStore);
75+
keyManagerFactory.init(keyStore, mqSecurityProperties.getKeystorePassword().toCharArray());
76+
sslContext = SSLContext.getInstance(mqSecurityProperties.getSslProtocol());
77+
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
78+
} catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyManagementException e1) {
79+
logger.error("Failed!", e1);
80+
}
81+
return sslContext.getSocketFactory();
82+
}
83+
84+
private static KeyStore getStore(String storePath, String storePassword) {
85+
KeyStore keyStore = null;
86+
try (FileInputStream keyStoreInput = new FileInputStream(storePath)) {
87+
keyStore = KeyStore.getInstance("JKS");
88+
keyStore.load(keyStoreInput, storePassword.toCharArray());
89+
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
90+
logger.error("Failed to get key or trust store: ", e);
91+
}
92+
return keyStore;
5993
}
6094

6195
/**
6296
* Method establishes connection with queue manager.
6397
*
6498
* @param qmNqme - queue manager's name.
65-
* @param connectionProperties - prepared structure with all parameters transformed into queue manager's format. See {@link #createMQConnectionParams(String, int, String, String, String, boolean)} for more info.
99+
* @param connectionProperties - prepared structure with all parameters transformed into queue manager's format. See {@link #createMQConnectionParams(Config config)} for more info.
66100
*/
67-
public void establish(String qmNqme, Hashtable<String, Object> connectionProperties) throws MQException {
68-
queueManager = new MQQueueManager(qmNqme, connectionProperties);
101+
public void establish(String qmNqme, Map<String, Object> connectionProperties) throws MQException {
102+
queueManager = new MQQueueManager(qmNqme, new Hashtable<>(connectionProperties));
69103
}
70104

71105
/**
@@ -100,4 +134,5 @@ public MQTopic createTopic(String topic) throws MQException {
100134
public MQQueueManager getQueueManager() {
101135
return this.queueManager;
102136
}
137+
103138
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package ru.cinimex.exporter.mq;
2+
3+
public class MQSecurityProperties {
4+
private boolean useTLS;
5+
private String keystorePath;
6+
private String keystorePassword;
7+
private String truststorePath;
8+
private String truststorePassword;
9+
private String sslProtocol;
10+
private String cipherSuite;
11+
12+
public MQSecurityProperties(boolean useTLS, String keystorePath, String keystorePassword, String truststorePath, String truststorePassword, String sslProtocol, String cipherSuite) {
13+
this.useTLS = useTLS;
14+
this.keystorePath = keystorePath;
15+
this.keystorePassword = keystorePassword;
16+
this.truststorePath = truststorePath;
17+
this.truststorePassword = truststorePassword;
18+
this.sslProtocol = sslProtocol;
19+
this.cipherSuite = cipherSuite;
20+
}
21+
22+
public String getKeystorePath() {
23+
return keystorePath;
24+
}
25+
26+
public String getKeystorePassword() {
27+
return keystorePassword;
28+
}
29+
30+
public String getTruststorePath() {
31+
return truststorePath;
32+
}
33+
34+
public String getTruststorePassword() {
35+
return truststorePassword;
36+
}
37+
38+
public String getSslProtocol() {
39+
return sslProtocol;
40+
}
41+
42+
public String getCipherSuite() {
43+
return cipherSuite;
44+
}
45+
46+
public boolean isUseTLS() {
47+
return useTLS;
48+
}
49+
}

src/main/java/ru/cinimex/exporter/mq/MQSubscriberManager.java

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.ibm.mq.MQException;
44
import org.apache.logging.log4j.LogManager;
55
import org.apache.logging.log4j.Logger;
6+
import ru.cinimex.exporter.Config;
67
import ru.cinimex.exporter.mq.pcf.PCFElement;
78

89
import java.util.*;
@@ -15,25 +16,19 @@
1516
*/
1617
public class MQSubscriberManager {
1718
private static final Logger logger = LogManager.getLogger(MQSubscriberManager.class);
18-
private Hashtable<String, Object> connectionProperties;
19+
private Map<String, Object> connectionProperties;
1920
private String queueManagerName;
2021
private ArrayList<MQSubscriber> subscribers;
2122
private ScheduledExecutorService executor;
2223

2324
/**
2425
* Constructor sets params for connecting to target queue manager.
2526
*
26-
* @param host - host, where queue manager is located.
27-
* @param port - queue manager's port.
28-
* @param channel - queue manager's channel.
29-
* @param qmName - queue manager's name.
30-
* @param user - user, which has enough privilege on the queue manager (optional).
31-
* @param password - password, which is required to establish connection with queue manager (optional).
32-
* @param useMQCSP - flag, which indicates, if MQCSP auth should be used.
27+
* @param config - object containing different properties
3328
*/
34-
public MQSubscriberManager(String host, int port, String channel, String qmName, String user, String password, boolean useMQCSP) {
35-
connectionProperties = MQConnection.createMQConnectionParams(host, port, channel, user, password, useMQCSP);
36-
queueManagerName = qmName;
29+
public MQSubscriberManager(Config config) {
30+
connectionProperties = MQConnection.createMQConnectionParams(config);
31+
queueManagerName = config.getQmgrName();
3732
}
3833

3934
/**
@@ -64,7 +59,7 @@ public void runSubscribers(List<PCFElement> elements, List<MQObject> objects, bo
6459
if (!subscribers.isEmpty()) {
6560
logger.info("Successfully launched {} subscribers!", subscribers.size());
6661
} else {
67-
logger.warn("Didn't launch any subscriber. Exporter finishes it's work!", subscribers.size());
62+
logger.warn("Didn't launch any subscriber. Exporter finishes it's work!");
6863
System.exit(1);
6964
}
7065

@@ -110,7 +105,7 @@ private void addTopicSubscriber(MQObject object, PCFElement element, int timeout
110105
PCFElement objElement = new PCFElement(element.getTopicString(), element.getRows());
111106
objElement.formatTopicString(object.getName());
112107
try {
113-
subscribers.add(new MQTopicSubscriber(objElement, queueManagerName, connectionProperties, timeout, queueManagerName, object.getName()));
108+
subscribers.add(new MQTopicSubscriber(objElement, queueManagerName, new Hashtable<>(connectionProperties), timeout, queueManagerName, object.getName()));
114109
} catch (MQException e) {
115110
logger.error("Error during creating topic subscriber: ", e);
116111
}
@@ -125,7 +120,7 @@ private void addTopicSubscriber(MQObject object, PCFElement element, int timeout
125120
*/
126121
private void addTopicSubscriber(PCFElement element, int timeout) {
127122
try {
128-
subscribers.add(new MQTopicSubscriber(element, queueManagerName, connectionProperties, timeout, queueManagerName));
123+
subscribers.add(new MQTopicSubscriber(element, queueManagerName, new Hashtable<>(connectionProperties), timeout, queueManagerName));
129124
} catch (MQException e) {
130125
logger.error("Error during creating topic subscriber: ", e);
131126
}
@@ -142,7 +137,7 @@ private void addPCFSubscribers(Map<MQObject.MQType, ArrayList<MQObject>> objects
142137
executor = Executors.newScheduledThreadPool(corePoolSize);
143138
for (Map.Entry<MQObject.MQType, ArrayList<MQObject>> entry : objects.entrySet()) {
144139
if (!entry.getValue().isEmpty()) {
145-
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, entry.getValue());
140+
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, new Hashtable<>(connectionProperties), entry.getValue());
146141
subscribers.add(subscriber);
147142
logger.debug("Starting subscriber for sending direct PCF commands to retrieve statistics about object with type {} and name {}.", entry.getKey().name());
148143
executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS);
@@ -159,12 +154,12 @@ private void addPCFSubscribers(Map<MQObject.MQType, ArrayList<MQObject>> objects
159154
*/
160155
private void addPCFSubscribers(List<MQObject> objects, int interval) {
161156
int corePoolSize = objects.size();
162-
ScheduledExecutorService executor = Executors.newScheduledThreadPool(corePoolSize);
157+
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);
163158
for (MQObject object : objects) {
164-
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, object);
159+
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, new Hashtable<>(connectionProperties), object);
165160
subscribers.add(subscriber);
166161
logger.debug("Starting subscriber for sending direct PCF commands to retrieve statistics about object with type {} and name {}.", object.getType().name(), object.getName());
167-
executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS);
162+
scheduledExecutorService.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS);
168163
logger.debug("Subscriber for sending direct PCF commands to retrieve statistics about object with type {} and name {} successfully started.", object.getType().name(), object.getName());
169164
}
170165
}

src/main/java/ru/cinimex/exporter/mq/MQTopicSubscriber.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public void stopProcessing() {
8484
/**
8585
* Starts subscriber.
8686
*/
87+
@Override
8788
public void run() {
8889
try {
8990
topic = connection.createTopic(element.getTopicString());

0 commit comments

Comments
 (0)