Skip to content

Commit d50b721

Browse files
authored
Added support for property based ssl configuration for JmxScraper (#1361)
* Added support for property based ssl configuration for JmxScraper Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Removed ssl properties from exporter.sh Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Made tests passing Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Reverted name Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Removed unused import Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Added documentation Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Retain existing ssl configuration Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * Reduce code duplication Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> * rename variable Signed-off-by: Hakky54 <hakangoudberg@hotmail.com> --------- Signed-off-by: Hakky54 <hakangoudberg@hotmail.com>
1 parent 61bb506 commit d50b721

File tree

11 files changed

+473
-19
lines changed

11 files changed

+473
-19
lines changed

collector/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
<artifactId>snakeyaml</artifactId>
6060
<version>2.5</version>
6161
</dependency>
62+
<dependency>
63+
<groupId>io.github.hakky54</groupId>
64+
<artifactId>ayza</artifactId>
65+
<version>10.0.2</version>
66+
</dependency>
6267
<dependency>
6368
<groupId>org.junit.jupiter</groupId>
6469
<artifactId>junit-jupiter</artifactId>

collector/src/main/java/io/prometheus/jmx/JmxCollector.java

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,22 @@
3434
import java.io.InputStream;
3535
import java.io.PrintWriter;
3636
import java.io.StringWriter;
37+
import java.nio.file.Path;
38+
import java.nio.file.Paths;
3739
import java.util.ArrayList;
40+
import java.util.Collections;
3841
import java.util.HashMap;
3942
import java.util.Iterator;
4043
import java.util.LinkedHashMap;
4144
import java.util.LinkedList;
4245
import java.util.List;
4346
import java.util.Map;
47+
import java.util.Optional;
4448
import java.util.TreeMap;
4549
import java.util.regex.Matcher;
4650
import java.util.regex.Pattern;
51+
import java.util.stream.Collectors;
52+
import java.util.stream.Stream;
4753
import javax.management.MalformedObjectNameException;
4854
import javax.management.ObjectName;
4955
import org.yaml.snakeyaml.Yaml;
@@ -85,6 +91,34 @@ static class Rule {
8591
ArrayList<String> labelValues;
8692
}
8793

94+
static class SslProperties {
95+
boolean enabled = false;
96+
KeyStoreProperties keyStoreProperties;
97+
KeyStoreProperties trustStoreProperties;
98+
List<String> protocols = Collections.emptyList();
99+
List<String> ciphers = Collections.emptyList();
100+
101+
public SslProperties() {}
102+
103+
public SslProperties(boolean enabled) {
104+
this.enabled = enabled;
105+
}
106+
107+
public Optional<KeyStoreProperties> getKeyStoreProperties() {
108+
return Optional.ofNullable(keyStoreProperties);
109+
}
110+
111+
public Optional<KeyStoreProperties> getTrustStoreProperties() {
112+
return Optional.ofNullable(trustStoreProperties);
113+
}
114+
}
115+
116+
static class KeyStoreProperties {
117+
Path path;
118+
String type;
119+
String password;
120+
}
121+
88122
/** Class to implement MetricCustomizer */
89123
public static class MetricCustomizer {
90124
MBeanFilter mbeanFilter;
@@ -114,7 +148,7 @@ private static class Config {
114148
String jmxUrl = "";
115149
String username = "";
116150
String password = "";
117-
boolean ssl = false;
151+
SslProperties sslProperties = new SslProperties();
118152
boolean lowercaseOutputName;
119153
boolean lowercaseOutputLabelNames;
120154
boolean inferCounterTypeFromName;
@@ -317,8 +351,41 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
317351
cfg.password = VariableResolver.resolveVariable(password);
318352
}
319353

320-
if (yamlConfig.containsKey("ssl")) {
321-
cfg.ssl = (Boolean) yamlConfig.get("ssl");
354+
if (yamlConfig.containsKey("ssl") && yamlConfig.get("ssl") instanceof Boolean) {
355+
cfg.sslProperties.enabled = (Boolean) yamlConfig.get("ssl");
356+
}
357+
358+
if (yamlConfig.containsKey("ssl") && yamlConfig.get("ssl") instanceof Map) {
359+
Map<String, Object> configSsl = (Map<String, Object>) yamlConfig.get("ssl");
360+
if (configSsl.containsKey("enabled")) {
361+
cfg.sslProperties.enabled = (Boolean) configSsl.get("enabled");
362+
}
363+
364+
if (configSsl.containsKey("keyStore")) {
365+
Map<String, Object> configKeyStore =
366+
(Map<String, Object>) configSsl.get("keyStore");
367+
cfg.sslProperties.keyStoreProperties = getKeyStoreProperties(configKeyStore);
368+
}
369+
370+
if (configSsl.containsKey("trustStore")) {
371+
Map<String, Object> configKeyStore =
372+
(Map<String, Object>) configSsl.get("trustStore");
373+
cfg.sslProperties.trustStoreProperties = getKeyStoreProperties(configKeyStore);
374+
}
375+
376+
if (configSsl.containsKey("protocols")) {
377+
cfg.sslProperties.protocols =
378+
Stream.of(((String) configSsl.get("protocols")).split(","))
379+
.map(String::trim)
380+
.collect(Collectors.toList());
381+
}
382+
383+
if (configSsl.containsKey("ciphers")) {
384+
cfg.sslProperties.ciphers =
385+
Stream.of(((String) configSsl.get("ciphers")).split(","))
386+
.map(String::trim)
387+
.collect(Collectors.toList());
388+
}
322389
}
323390

324391
if (yamlConfig.containsKey("lowercaseOutputName")) {
@@ -527,6 +594,20 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
527594
return cfg;
528595
}
529596

597+
private KeyStoreProperties getKeyStoreProperties(Map<String, Object> configKeyStore) {
598+
KeyStoreProperties keyStoreProperties = new KeyStoreProperties();
599+
if (configKeyStore.containsKey("filename")) {
600+
keyStoreProperties.path = Paths.get((String) configKeyStore.get("filename"));
601+
}
602+
if (configKeyStore.containsKey("type")) {
603+
keyStoreProperties.type = (String) configKeyStore.get("type");
604+
}
605+
if (configKeyStore.containsKey("password")) {
606+
keyStoreProperties.password = (String) configKeyStore.get("password");
607+
}
608+
return keyStoreProperties;
609+
}
610+
530611
/**
531612
* Convert name to snake case and lower case.
532613
*
@@ -929,7 +1010,7 @@ public MetricSnapshots collect() {
9291010
config.jmxUrl,
9301011
config.username,
9311012
config.password,
932-
config.ssl,
1013+
config.sslProperties,
9331014
config.includeObjectNames,
9341015
config.excludeObjectNames,
9351016
config.objectNameAttributeFilter,

collector/src/main/java/io/prometheus/jmx/JmxScraper.java

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.prometheus.jmx;
1818

19+
import io.prometheus.jmx.JmxCollector.SslProperties;
1920
import io.prometheus.jmx.logger.Logger;
2021
import io.prometheus.jmx.logger.LoggerFactory;
2122
import java.io.IOException;
@@ -30,6 +31,7 @@
3031
import java.util.Optional;
3132
import java.util.Set;
3233
import java.util.TreeSet;
34+
import java.util.concurrent.Callable;
3335
import java.util.stream.Collectors;
3436
import javax.management.Attribute;
3537
import javax.management.AttributeList;
@@ -49,6 +51,8 @@
4951
import javax.management.remote.rmi.RMIConnectorServer;
5052
import javax.naming.Context;
5153
import javax.rmi.ssl.SslRMIClientSocketFactory;
54+
import nl.altindag.ssl.SSLFactory;
55+
import nl.altindag.ssl.util.ProviderUtils;
5256

5357
/** Class to implement JmxScraper */
5458
class JmxScraper {
@@ -84,7 +88,7 @@ void recordBean(
8488
private final String jmxUrl;
8589
private final String username;
8690
private final String password;
87-
private final boolean ssl;
91+
private final SslProperties sslProperties;
8892
private final List<ObjectName> includeObjectNames, excludeObjectNames;
8993
private final List<JmxCollector.MetricCustomizer> metricCustomizers;
9094
private final ObjectNameAttributeFilter objectNameAttributeFilter;
@@ -96,7 +100,7 @@ void recordBean(
96100
* @param jmxUrl jmxUrl
97101
* @param username username
98102
* @param password password
99-
* @param ssl ssl
103+
* @param sslProperties sslProperties
100104
* @param includeObjectNames includeObjectNames
101105
* @param excludeObjectNames excludeObjectNames
102106
* @param objectNameAttributeFilter objectNameAttributeFilter
@@ -107,7 +111,7 @@ public JmxScraper(
107111
String jmxUrl,
108112
String username,
109113
String password,
110-
boolean ssl,
114+
SslProperties sslProperties,
111115
List<ObjectName> includeObjectNames,
112116
List<ObjectName> excludeObjectNames,
113117
ObjectNameAttributeFilter objectNameAttributeFilter,
@@ -118,7 +122,7 @@ public JmxScraper(
118122
this.receiver = receiver;
119123
this.username = username;
120124
this.password = password;
121-
this.ssl = ssl;
125+
this.sslProperties = sslProperties;
122126
this.includeObjectNames = includeObjectNames;
123127
this.excludeObjectNames = excludeObjectNames;
124128
this.metricCustomizers = metricCustomizers;
@@ -145,20 +149,23 @@ public void doScrape() throws Exception {
145149
String[] credent = new String[] {username, password};
146150
environment.put(javax.management.remote.JMXConnector.CREDENTIALS, credent);
147151
}
148-
if (ssl) {
152+
if (sslProperties.enabled) {
149153
environment.put(Context.SECURITY_PROTOCOL, "ssl");
154+
155+
SSLFactory sslFactory = createSslFactory();
156+
ProviderUtils.configure(sslFactory);
157+
150158
SslRMIClientSocketFactory clientSocketFactory = new SslRMIClientSocketFactory();
151159
environment.put(
152-
RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
153-
clientSocketFactory);
154-
160+
RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientSocketFactory);
155161
if (!"true".equalsIgnoreCase(System.getenv("RMI_REGISTRY_SSL_DISABLED"))) {
156162
environment.put("com.sun.jndi.rmi.factory.socket", clientSocketFactory);
157163
}
158164
}
159165

160166
jmxc = JMXConnectorFactory.connect(new JMXServiceURL(jmxUrl), environment);
161167
beanConn = jmxc.getMBeanServerConnection();
168+
ProviderUtils.remove();
162169
}
163170
try {
164171
// Query MBean names, see #89 for reasons queryMBeans() is used instead of queryNames()
@@ -192,6 +199,61 @@ public void doScrape() throws Exception {
192199
}
193200
}
194201

202+
/**
203+
* Attempts to resolve the ssl configuration defined in the yaml file
204+
* Next to that it also attempts to read the following system properties:
205+
* <p>
206+
* <pre>
207+
* - javax.net.ssl.keyStore
208+
* - javax.net.ssl.keyStorePassword
209+
* - javax.net.ssl.keyStoreType
210+
* - javax.net.ssl.keyStoreProvider
211+
* - javax.net.ssl.trustStore
212+
* - javax.net.ssl.trustStorePassword
213+
* - javax.net.ssl.trustStoreType
214+
* - javax.net.ssl.trustStoreProvider
215+
* - https.protocols
216+
* - https.cipherSuites
217+
* </pre>
218+
*/
219+
private SSLFactory createSslFactory() {
220+
SSLFactory.Builder sslFactoryBuilder = SSLFactory.builder().withDefaultTrustMaterial();
221+
sslProperties
222+
.getKeyStoreProperties()
223+
.ifPresent(
224+
props ->
225+
sslFactoryBuilder.withIdentityMaterial(
226+
props.path, props.password.toCharArray(), props.type));
227+
sslProperties
228+
.getTrustStoreProperties()
229+
.ifPresent(
230+
props ->
231+
sslFactoryBuilder.withTrustMaterial(
232+
props.path, props.password.toCharArray(), props.type));
233+
234+
if (!sslProperties.protocols.isEmpty()) {
235+
sslFactoryBuilder.withProtocols(sslProperties.protocols.toArray(new String[0]));
236+
}
237+
if (!sslProperties.ciphers.isEmpty()) {
238+
sslFactoryBuilder.withCiphers(sslProperties.ciphers.toArray(new String[0]));
239+
}
240+
241+
callSafely(sslFactoryBuilder::withSystemPropertyDerivedIdentityMaterial,
242+
sslFactoryBuilder::withSystemPropertyDerivedTrustMaterial,
243+
sslFactoryBuilder::withSystemPropertyDerivedProtocols,
244+
sslFactoryBuilder::withSystemPropertyDerivedCiphers);
245+
246+
return sslFactoryBuilder.build();
247+
}
248+
249+
private void callSafely(Callable<?>... callables) {
250+
for (Callable<?> callable : callables) {
251+
try {
252+
callable.call();
253+
} catch (Exception ignored) {}
254+
}
255+
}
256+
195257
private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
196258
MBeanInfo mBeanInfo;
197259

@@ -574,7 +636,9 @@ public static void main(String[] args) throws Exception {
574636
args[0],
575637
args[1],
576638
args[2],
577-
(args.length > 3 && "ssl".equalsIgnoreCase(args[3])),
639+
(args.length > 3 && "ssl".equalsIgnoreCase(args[3]))
640+
? new SslProperties(true)
641+
: new SslProperties(false),
578642
objectNames,
579643
new LinkedList<>(),
580644
objectNameAttributeFilter,
@@ -587,7 +651,7 @@ public static void main(String[] args) throws Exception {
587651
args[0],
588652
"",
589653
"",
590-
false,
654+
new SslProperties(false),
591655
objectNames,
592656
new LinkedList<>(),
593657
objectNameAttributeFilter,
@@ -600,7 +664,7 @@ public static void main(String[] args) throws Exception {
600664
"",
601665
"",
602666
"",
603-
false,
667+
new SslProperties(false),
604668
objectNames,
605669
new LinkedList<>(),
606670
objectNameAttributeFilter,

integration_test_suite/integration_tests/src/main/java/io/prometheus/jmx/test/support/http/HttpClient.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,13 @@ private static byte[] readBytes(InputStream inputStream) throws IOException {
233233

234234
private static CloseableHttpClient getHttpClient(
235235
int connectTimeout, int readTimeout, SSLContext sslContext) {
236-
if (connectTimeout != CONNECT_TIMEOUT || readTimeout != READ_TIMEOUT || sslContext != null) {
236+
if (connectTimeout != CONNECT_TIMEOUT
237+
|| readTimeout != READ_TIMEOUT
238+
|| sslContext != null) {
237239
return createHttpClient(
238-
connectTimeout,
239-
readTimeout,
240-
sslContext != null ? sslContext : UNSAFE_SSLCONTEXT);
240+
connectTimeout,
241+
readTimeout,
242+
sslContext != null ? sslContext : UNSAFE_SSLCONTEXT);
241243
}
242244
return defaultHttpClient;
243245
}

0 commit comments

Comments
 (0)