Skip to content

Commit 636adba

Browse files
committed
Added support for property based ssl configuration for JmxScraper
Signed-off-by: Hakky54 <hakangoudberg@hotmail.com>
1 parent 5226d76 commit 636adba

File tree

15 files changed

+500
-24
lines changed

15 files changed

+500
-24
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.1</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: 82 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,38 @@ 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 Map) {
355+
Map<String, Object> configSsl = (Map<String, Object>) yamlConfig.get("ssl");
356+
357+
if (configSsl.containsKey("enabled")) {
358+
cfg.sslProperties.enabled = (Boolean) configSsl.get("enabled");
359+
}
360+
361+
if (configSsl.containsKey("keyStore")) {
362+
Map<String, Object> configKeyStore =
363+
(Map<String, Object>) configSsl.get("keyStore");
364+
cfg.sslProperties.keyStoreProperties = getKeyStoreProperties(configKeyStore);
365+
}
366+
367+
if (configSsl.containsKey("trustStore")) {
368+
Map<String, Object> configKeyStore =
369+
(Map<String, Object>) configSsl.get("trustStore");
370+
cfg.sslProperties.trustStoreProperties = getKeyStoreProperties(configKeyStore);
371+
}
372+
373+
if (configSsl.containsKey("protocols")) {
374+
cfg.sslProperties.protocols =
375+
Stream.of(((String) configSsl.get("protocols")).split(","))
376+
.map(String::trim)
377+
.collect(Collectors.toList());
378+
}
379+
380+
if (configSsl.containsKey("ciphers")) {
381+
cfg.sslProperties.ciphers =
382+
Stream.of(((String) configSsl.get("ciphers")).split(","))
383+
.map(String::trim)
384+
.collect(Collectors.toList());
385+
}
322386
}
323387

324388
if (yamlConfig.containsKey("lowercaseOutputName")) {
@@ -527,6 +591,20 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
527591
return cfg;
528592
}
529593

594+
private KeyStoreProperties getKeyStoreProperties(Map<String, Object> configKeyStore) {
595+
KeyStoreProperties keyStoreProperties = new KeyStoreProperties();
596+
if (configKeyStore.containsKey("filename")) {
597+
keyStoreProperties.path = Paths.get((String) configKeyStore.get("filename"));
598+
}
599+
if (configKeyStore.containsKey("type")) {
600+
keyStoreProperties.type = (String) configKeyStore.get("type");
601+
}
602+
if (configKeyStore.containsKey("password")) {
603+
keyStoreProperties.password = (String) configKeyStore.get("password");
604+
}
605+
return keyStoreProperties;
606+
}
607+
530608
/**
531609
* Convert name to snake case and lower case.
532610
*
@@ -929,7 +1007,7 @@ public MetricSnapshots collect() {
9291007
config.jmxUrl,
9301008
config.username,
9311009
config.password,
932-
config.ssl,
1010+
config.sslProperties,
9331011
config.includeObjectNames,
9341012
config.excludeObjectNames,
9351013
config.objectNameAttributeFilter,

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

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
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;
22+
import io.prometheus.jmx.ssl.EnhanceableSslRMIClientSocketFactory;
2123
import java.io.IOException;
2224
import java.lang.management.ManagementFactory;
25+
import java.rmi.server.RMIClientSocketFactory;
2326
import java.util.Collections;
2427
import java.util.HashMap;
2528
import java.util.HashSet;
@@ -49,6 +52,7 @@
4952
import javax.management.remote.rmi.RMIConnectorServer;
5053
import javax.naming.Context;
5154
import javax.rmi.ssl.SslRMIClientSocketFactory;
55+
import nl.altindag.ssl.SSLFactory;
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,15 +149,13 @@ 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");
150-
SslRMIClientSocketFactory clientSocketFactory = new SslRMIClientSocketFactory();
154+
RMIClientSocketFactory socketFactory = createSecureRMIClient();
151155
environment.put(
152-
RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
153-
clientSocketFactory);
154-
156+
RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, socketFactory);
155157
if (!"true".equalsIgnoreCase(System.getenv("RMI_REGISTRY_SSL_DISABLED"))) {
156-
environment.put("com.sun.jndi.rmi.factory.socket", clientSocketFactory);
158+
environment.put("com.sun.jndi.rmi.factory.socket", socketFactory);
157159
}
158160
}
159161

@@ -192,6 +194,46 @@ public void doScrape() throws Exception {
192194
}
193195
}
194196

197+
private RMIClientSocketFactory createSecureRMIClient() {
198+
if (!sslProperties.getKeyStoreProperties().isPresent()
199+
|| !sslProperties.getTrustStoreProperties().isPresent()) {
200+
return new SslRMIClientSocketFactory();
201+
}
202+
203+
SSLFactory.Builder sslFactoryBuilder = SSLFactory.builder();
204+
sslProperties
205+
.getKeyStoreProperties()
206+
.ifPresent(
207+
props ->
208+
sslFactoryBuilder.withIdentityMaterial(
209+
props.path, props.password.toCharArray(), props.type));
210+
sslProperties
211+
.getTrustStoreProperties()
212+
.ifPresent(
213+
props ->
214+
sslFactoryBuilder.withTrustMaterial(
215+
props.path, props.password.toCharArray(), props.type));
216+
if (!sslProperties.protocols.isEmpty()) {
217+
sslFactoryBuilder.withProtocols(sslProperties.protocols.toArray(new String[0]));
218+
}
219+
if (!sslProperties.ciphers.isEmpty()) {
220+
sslFactoryBuilder.withCiphers(sslProperties.ciphers.toArray(new String[0]));
221+
}
222+
223+
try {
224+
sslFactoryBuilder.withSystemPropertyDerivedIdentityMaterial();
225+
} catch (Exception ignored) {
226+
}
227+
228+
try {
229+
sslFactoryBuilder.withSystemPropertyDerivedTrustMaterial();
230+
} catch (Exception ignored) {
231+
}
232+
233+
SSLFactory sslFactory = sslFactoryBuilder.build();
234+
return new EnhanceableSslRMIClientSocketFactory(sslFactory);
235+
}
236+
195237
private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
196238
MBeanInfo mBeanInfo;
197239

@@ -574,7 +616,9 @@ public static void main(String[] args) throws Exception {
574616
args[0],
575617
args[1],
576618
args[2],
577-
(args.length > 3 && "ssl".equalsIgnoreCase(args[3])),
619+
(args.length > 3 && "ssl".equalsIgnoreCase(args[3]))
620+
? new SslProperties(true)
621+
: new SslProperties(false),
578622
objectNames,
579623
new LinkedList<>(),
580624
objectNameAttributeFilter,
@@ -587,7 +631,7 @@ public static void main(String[] args) throws Exception {
587631
args[0],
588632
"",
589633
"",
590-
false,
634+
new SslProperties(false),
591635
objectNames,
592636
new LinkedList<>(),
593637
objectNameAttributeFilter,
@@ -600,7 +644,7 @@ public static void main(String[] args) throws Exception {
600644
"",
601645
"",
602646
"",
603-
false,
647+
new SslProperties(false),
604648
objectNames,
605649
new LinkedList<>(),
606650
objectNameAttributeFilter,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.prometheus.jmx.ssl;
2+
3+
import java.io.IOException;
4+
import java.io.Serializable;
5+
import java.net.Socket;
6+
import java.rmi.server.RMIClientSocketFactory;
7+
import nl.altindag.ssl.SSLFactory;
8+
9+
public final class EnhanceableSslRMIClientSocketFactory
10+
implements RMIClientSocketFactory, Serializable {
11+
12+
private static final long serialVersionUID = 7523870139487859635L;
13+
14+
private final SSLFactory sslFactory;
15+
16+
public EnhanceableSslRMIClientSocketFactory(SSLFactory sslFactory) {
17+
this.sslFactory = sslFactory;
18+
}
19+
20+
@Override
21+
public Socket createSocket(String host, int port) throws IOException {
22+
return sslFactory.getSslSocketFactory().createSocket(host, port);
23+
}
24+
25+
@Override
26+
public boolean equals(Object obj) {
27+
if (obj == null) return false;
28+
if (obj == this) return true;
29+
return this.getClass().equals(obj.getClass());
30+
}
31+
32+
@Override
33+
public int hashCode() {
34+
return this.getClass().hashCode();
35+
}
36+
}

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)