Skip to content

Commit

Permalink
Merge pull request #124 from Cinimex-Informatica/feature/issue119_tls…
Browse files Browse the repository at this point in the history
…_support

add TLS support
  • Loading branch information
willardgibbs committed Apr 16, 2019
2 parents d0f7f82 + 92997a9 commit 81dff84
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 52 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,21 @@ qmgrConnectionParams:
# How long to wait until metrics are published by queue manager (milliseconds).
# Value must be at least 10000 (periodicity with which metrics are published by MQ).
connTimeout: 12000

# Use TLS connection to queue manager? If useTLS equals "false" than all connection parameters below will be ignored.
useTLS: true
# Path to keystore file
keystorePath: /opt/mq_exporter/keystores/keystore.jks
# Keystore password
keystorePassword: testpass2
# Path to truststore file
truststorePath: /opt/mq_exporter/keystores/truststore.jks
# Truststore password
truststorePassword: testpass2
# SSL protocol
sslProtocol: TLSv1.2
# CipherSuite
cipherSuite: TLS_RSA_WITH_AES_256_CBC_SHA256

# Prometheus connection information -------------------------------
prometheusEndpointParams:
# URL and port which will be used to expose metrics for Prometheus.
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/ru/cinimex/exporter/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.yaml.snakeyaml.Yaml;
import ru.cinimex.exporter.mq.MQSecurityProperties;

import java.io.BufferedReader;
import java.io.File;
Expand Down Expand Up @@ -34,6 +35,7 @@ public class Config {
private boolean sendPCFCommands;
private boolean usePCFWildcards;
private int scrapeInterval;
private MQSecurityProperties mqSecurityProperties;

public Config(String path) {
Yaml file = new Yaml();
Expand Down Expand Up @@ -65,6 +67,21 @@ public Config(String path) {
this.sendPCFCommands = (boolean) pcfParameters.get("sendPCFCommands");
this.usePCFWildcards = (boolean) pcfParameters.get("usePCFWildcards");
this.scrapeInterval = (Integer) pcfParameters.get("scrapeInterval");
boolean useTLS = (boolean) qmgrConnectionParams.get("useTLS");
if (useTLS) {
logger.debug("Secured connection to queue manager will be used");
mqSecurityProperties = new MQSecurityProperties(
useTLS,
(String) qmgrConnectionParams.get("keystorePath"),
(String) qmgrConnectionParams.get("keystorePassword"),
(String) qmgrConnectionParams.get("truststorePath"),
(String) qmgrConnectionParams.get("truststorePassword"),
(String) qmgrConnectionParams.get("sslProtocol"),
(String) qmgrConnectionParams.get("cipherSuite")
);
} else {
logger.debug("Unsecured connection to queue manager will be used");
}
logger.info("Successfully parsed configuration file!");
}

Expand Down Expand Up @@ -132,4 +149,7 @@ public List<String> getQueues() {
return queues;
}

public MQSecurityProperties getMqSecurityProperties() {
return mqSecurityProperties;
}
}
7 changes: 4 additions & 3 deletions src/main/java/ru/cinimex/exporter/ExporterLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static void main(String[] args) {
}

MetricsManager.initMetrics(elements, monitoringTypes);
MQSubscriberManager manager = new MQSubscriberManager(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp());
MQSubscriberManager manager = new MQSubscriberManager(config);
manager.runSubscribers(elements, objects, config.sendPCFCommands(), config.usePCFWildcards(),
config.getScrapeInterval(), config.getConnTimeout());
try {
Expand All @@ -87,7 +87,7 @@ private static ArrayList<PCFElement> getAllPublishedMetrics(Config config) {
gmo.options = GMO;
gmo.waitInterval = 30000;
try {
connection.establish(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp());
connection.establish(config.getQmgrName(), MQConnection.createMQConnectionParams(config));
topic = connection.createTopic(String.format(TOPIC_STRING, config.getQmgrName()));
MQMessage msg = getEmptyMessage();
topic.get(msg, gmo);
Expand All @@ -107,7 +107,8 @@ private static ArrayList<PCFElement> getAllPublishedMetrics(Config config) {
elements.addAll(PCFDataParser.getPCFElements(pcfResponse));
}
}
} catch (MQException | IOException e) {
} catch (MQException |
IOException e) {
logger.error("Failed!", e);
} finally {
try {
Expand Down
95 changes: 65 additions & 30 deletions src/main/java/ru/cinimex/exporter/mq/MQConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,23 @@
import com.ibm.mq.constants.MQConstants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ru.cinimex.exporter.Config;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
* Class represents MQ connection.
Expand All @@ -20,52 +35,71 @@ public class MQConnection {
/**
* Method creates connection properties Hashtable from connection parameters.
*
* @param host - host, where queue manager is located.
* @param port - queue manager's port.
* @param channel - queue manager's channel.
* @param user - user, which has enough privilege on the queue manager (optional).
* @param password - password, which is required to establish connection with queue manager (optional).
* @param useMQCSP - flag, which indicates, if MQCSP auth should be used.
* @param config - object containing different properties.
* @return - returns prepared structure with all parameters transformed into queue manager's format.
*/
protected static Hashtable<String, Object> createMQConnectionParams(String host, int port, String channel, String user, String password, boolean useMQCSP) {
Hashtable<String, Object> properties = new Hashtable<>();
properties.put(MQConstants.TRANSPORT_PROPERTY, host == null ? MQConstants.TRANSPORT_MQSERIES_BINDINGS : MQConstants.TRANSPORT_MQSERIES_CLIENT);
if (host != null) properties.put(MQConstants.HOST_NAME_PROPERTY, host);
if (port != 0) properties.put(MQConstants.PORT_PROPERTY, port);
if (channel != null) properties.put(MQConstants.CHANNEL_PROPERTY, channel);
if (user != null || password != null) {
if (useMQCSP) properties.put(MQConstants.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
if (user != null) properties.put(MQConstants.USER_ID_PROPERTY, user);
if (password != null) properties.put(MQConstants.PASSWORD_PROPERTY, password);
public static Map<String, Object> createMQConnectionParams(Config config) {
Map<String, Object> properties = new HashMap<>();
properties.put(MQConstants.TRANSPORT_PROPERTY, config.getQmgrHost() == null ? MQConstants.TRANSPORT_MQSERIES_BINDINGS : MQConstants.TRANSPORT_MQSERIES_CLIENT);
if (config.getQmgrHost() != null) properties.put(MQConstants.HOST_NAME_PROPERTY, config.getQmgrHost());
if (config.getQmgrPort() != 0) properties.put(MQConstants.PORT_PROPERTY, config.getQmgrPort());
if (config.getQmgrChannel() != null) properties.put(MQConstants.CHANNEL_PROPERTY, config.getQmgrChannel());
if (config.getUser() != null || config.getPassword() != null) {
if (config.useMqscp()) properties.put(MQConstants.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
if (config.getUser() != null) properties.put(MQConstants.USER_ID_PROPERTY, config.getUser());
if (config.getPassword() != null) properties.put(MQConstants.PASSWORD_PROPERTY, config.getPassword());
}
MQSecurityProperties mqSecurityProperties = config.getMqSecurityProperties();
if (mqSecurityProperties != null && mqSecurityProperties.isUseTLS()) {
properties.put(MQConstants.SSL_CIPHER_SUITE_PROPERTY, mqSecurityProperties.getCipherSuite());
properties.put(MQConstants.SSL_SOCKET_FACTORY_PROPERTY, getSslSocketFactory(mqSecurityProperties));
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
}
return properties;
}

/**
* Method establishes connection with queue manager.
* Method creates SSLSocketFactory from connection parameters.
*
* @param host - host, where queue manager is located.
* @param port - queue manager's port.
* @param channel - queue manager's channel.
* @param qmName - queue manager's name.
* @param user - user, which has enough privilege on the queue manager (optional).
* @param password - password, which is required to establish connection with queue manager (optional).
* @param useMQCSP - flag, which indicates, if MQCSP auth should be used.
* @param mqSecurityProperties - object containing security properties.
* @return - returns prepared SSLSocketFactory.
*/
public void establish(String host, int port, String channel, String qmName, String user, String password, boolean useMQCSP) throws MQException {
Hashtable<String, Object> connectionProperties = createMQConnectionParams(host, port, channel, user, password, useMQCSP);
queueManager = new MQQueueManager(qmName, connectionProperties);
private static SSLSocketFactory getSslSocketFactory(MQSecurityProperties mqSecurityProperties) {
KeyStore keyStore = getStore(mqSecurityProperties.getKeystorePath(), mqSecurityProperties.getKeystorePassword());
KeyStore trustStore = getStore(mqSecurityProperties.getTruststorePath(), mqSecurityProperties.getTruststorePassword());
SSLContext sslContext = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
keyManagerFactory.init(keyStore, mqSecurityProperties.getKeystorePassword().toCharArray());
sslContext = SSLContext.getInstance(mqSecurityProperties.getSslProtocol());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyManagementException e1) {
logger.error("Failed!", e1);
}
return sslContext.getSocketFactory();
}

private static KeyStore getStore(String storePath, String storePassword) {
KeyStore keyStore = null;
try (FileInputStream keyStoreInput = new FileInputStream(storePath)) {
keyStore = KeyStore.getInstance("JKS");
keyStore.load(keyStoreInput, storePassword.toCharArray());
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
logger.error("Failed to get key or trust store: ", e);
}
return keyStore;
}

/**
* Method establishes connection with queue manager.
*
* @param qmNqme - queue manager's name.
* @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.
* @param connectionProperties - prepared structure with all parameters transformed into queue manager's format. See {@link #createMQConnectionParams(Config config)} for more info.
*/
public void establish(String qmNqme, Hashtable<String, Object> connectionProperties) throws MQException {
queueManager = new MQQueueManager(qmNqme, connectionProperties);
public void establish(String qmNqme, Map<String, Object> connectionProperties) throws MQException {
queueManager = new MQQueueManager(qmNqme, new Hashtable<>(connectionProperties));
}

/**
Expand Down Expand Up @@ -100,4 +134,5 @@ public MQTopic createTopic(String topic) throws MQException {
public MQQueueManager getQueueManager() {
return this.queueManager;
}

}
49 changes: 49 additions & 0 deletions src/main/java/ru/cinimex/exporter/mq/MQSecurityProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ru.cinimex.exporter.mq;

public class MQSecurityProperties {
private boolean useTLS;
private String keystorePath;
private String keystorePassword;
private String truststorePath;
private String truststorePassword;
private String sslProtocol;
private String cipherSuite;

public MQSecurityProperties(boolean useTLS, String keystorePath, String keystorePassword, String truststorePath, String truststorePassword, String sslProtocol, String cipherSuite) {
this.useTLS = useTLS;
this.keystorePath = keystorePath;
this.keystorePassword = keystorePassword;
this.truststorePath = truststorePath;
this.truststorePassword = truststorePassword;
this.sslProtocol = sslProtocol;
this.cipherSuite = cipherSuite;
}

public String getKeystorePath() {
return keystorePath;
}

public String getKeystorePassword() {
return keystorePassword;
}

public String getTruststorePath() {
return truststorePath;
}

public String getTruststorePassword() {
return truststorePassword;
}

public String getSslProtocol() {
return sslProtocol;
}

public String getCipherSuite() {
return cipherSuite;
}

public boolean isUseTLS() {
return useTLS;
}
}
31 changes: 13 additions & 18 deletions src/main/java/ru/cinimex/exporter/mq/MQSubscriberManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.ibm.mq.MQException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ru.cinimex.exporter.Config;
import ru.cinimex.exporter.mq.pcf.PCFElement;

import java.util.*;
Expand All @@ -15,25 +16,19 @@
*/
public class MQSubscriberManager {
private static final Logger logger = LogManager.getLogger(MQSubscriberManager.class);
private Hashtable<String, Object> connectionProperties;
private Map<String, Object> connectionProperties;
private String queueManagerName;
private ArrayList<MQSubscriber> subscribers;
private ScheduledExecutorService executor;

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

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

Expand Down Expand Up @@ -110,7 +105,7 @@ private void addTopicSubscriber(MQObject object, PCFElement element, int timeout
PCFElement objElement = new PCFElement(element.getTopicString(), element.getRows());
objElement.formatTopicString(object.getName());
try {
subscribers.add(new MQTopicSubscriber(objElement, queueManagerName, connectionProperties, timeout, queueManagerName, object.getName()));
subscribers.add(new MQTopicSubscriber(objElement, queueManagerName, new Hashtable<>(connectionProperties), timeout, queueManagerName, object.getName()));
} catch (MQException e) {
logger.error("Error during creating topic subscriber: ", e);
}
Expand All @@ -125,7 +120,7 @@ private void addTopicSubscriber(MQObject object, PCFElement element, int timeout
*/
private void addTopicSubscriber(PCFElement element, int timeout) {
try {
subscribers.add(new MQTopicSubscriber(element, queueManagerName, connectionProperties, timeout, queueManagerName));
subscribers.add(new MQTopicSubscriber(element, queueManagerName, new Hashtable<>(connectionProperties), timeout, queueManagerName));
} catch (MQException e) {
logger.error("Error during creating topic subscriber: ", e);
}
Expand All @@ -142,7 +137,7 @@ private void addPCFSubscribers(Map<MQObject.MQType, ArrayList<MQObject>> objects
executor = Executors.newScheduledThreadPool(corePoolSize);
for (Map.Entry<MQObject.MQType, ArrayList<MQObject>> entry : objects.entrySet()) {
if (!entry.getValue().isEmpty()) {
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, entry.getValue());
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, new Hashtable<>(connectionProperties), entry.getValue());
subscribers.add(subscriber);
logger.debug("Starting subscriber for sending direct PCF commands to retrieve statistics about object with type {} and name {}.", entry.getKey().name());
executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS);
Expand All @@ -159,12 +154,12 @@ private void addPCFSubscribers(Map<MQObject.MQType, ArrayList<MQObject>> objects
*/
private void addPCFSubscribers(List<MQObject> objects, int interval) {
int corePoolSize = objects.size();
ScheduledExecutorService executor = Executors.newScheduledThreadPool(corePoolSize);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);
for (MQObject object : objects) {
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, object);
MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, new Hashtable<>(connectionProperties), object);
subscribers.add(subscriber);
logger.debug("Starting subscriber for sending direct PCF commands to retrieve statistics about object with type {} and name {}.", object.getType().name(), object.getName());
executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS);
logger.debug("Subscriber for sending direct PCF commands to retrieve statistics about object with type {} and name {} successfully started.", object.getType().name(), object.getName());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public void stopProcessing() {
/**
* Starts subscriber.
*/
@Override
public void run() {
try {
topic = connection.createTopic(element.getTopicString());
Expand Down
Loading

0 comments on commit 81dff84

Please sign in to comment.