Skip to content

Commit 99f445e

Browse files
committed
add trustStore and needClientAuth config to yaml (#834)
Signed-off-by: Gary Tully <gary.tully@gmail.com>
1 parent 367b16e commit 99f445e

File tree

13 files changed

+518
-18
lines changed

13 files changed

+518
-18
lines changed

docs/content/1.1.0/http-mode/ssl.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ httpServer:
2626
2727
2. Create a keystore and add your certificate
2828
29+
If you need to verify a clients certificate, you set needClientAuth and configure the trustStore parameters
30+
31+
```yaml
32+
httpServer:
33+
ssl:
34+
needClientAuth: true
35+
trustStore:
36+
filename: ca.jks
37+
type: JKS
38+
password: changeit
39+
keyStore:
40+
filename: localhost.jks
41+
password: changeit
42+
certificate:
43+
alias: localhost
44+
```
45+
46+
2947
### Configuration (using System properties)
3048
3149
1. Add configuration to your exporter YAML file
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
* Copyright (C) 2023-present The Prometheus jmx_exporter Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.prometheus.jmx.test.http.ssl;
18+
19+
import static io.prometheus.jmx.test.support.Assertions.assertCommonMetricsResponse;
20+
import static io.prometheus.jmx.test.support.Assertions.assertHealthyResponse;
21+
import static io.prometheus.jmx.test.support.metrics.MetricAssertion.assertMetric;
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
import io.prometheus.jmx.test.support.ExporterPath;
25+
import io.prometheus.jmx.test.support.ExporterTestEnvironment;
26+
import io.prometheus.jmx.test.support.JmxExporterMode;
27+
import io.prometheus.jmx.test.support.PKCS12KeyStoreExporterTestEnvironmentFilter;
28+
import io.prometheus.jmx.test.support.TestSupport;
29+
import io.prometheus.jmx.test.support.http.HttpClient;
30+
import io.prometheus.jmx.test.support.http.HttpResponse;
31+
import io.prometheus.jmx.test.support.metrics.Metric;
32+
import io.prometheus.jmx.test.support.metrics.MetricsContentType;
33+
import io.prometheus.jmx.test.support.metrics.MetricsParser;
34+
import java.io.IOException;
35+
import java.io.InputStream;
36+
import java.net.SocketException;
37+
import java.security.KeyManagementException;
38+
import java.security.KeyStore;
39+
import java.security.KeyStoreException;
40+
import java.security.NoSuchAlgorithmException;
41+
import java.security.UnrecoverableKeyException;
42+
import java.security.cert.CertificateException;
43+
import java.util.ArrayList;
44+
import java.util.Collection;
45+
import java.util.LinkedHashMap;
46+
import java.util.LinkedHashSet;
47+
import java.util.List;
48+
import java.util.Map;
49+
import java.util.Set;
50+
import java.util.stream.Stream;
51+
import javax.net.ssl.HttpsURLConnection;
52+
import javax.net.ssl.KeyManagerFactory;
53+
import javax.net.ssl.SSLContext;
54+
import javax.net.ssl.SSLSocketFactory;
55+
import javax.net.ssl.TrustManagerFactory;
56+
import org.assertj.core.util.Strings;
57+
import org.opentest4j.AssertionFailedError;
58+
import org.testcontainers.containers.Network;
59+
import org.verifyica.api.ArgumentContext;
60+
import org.verifyica.api.ClassContext;
61+
import org.verifyica.api.Trap;
62+
import org.verifyica.api.Verifyica;
63+
64+
public class SSLWithTrustStoreAndClientAuth {
65+
66+
private static final String BASE_URL = "https://localhost";
67+
68+
@Verifyica.ArgumentSupplier() // not parallel as the static HttpsURLConnection defaultSSLSocketFactory is manipulated
69+
public static Stream<ExporterTestEnvironment> arguments() {
70+
// Filter Java versions that don't support the PKCS12 keystore
71+
// format or don't support the required TLS cipher suites
72+
return ExporterTestEnvironment.createExporterTestEnvironments()
73+
.filter(new PKCS12KeyStoreExporterTestEnvironmentFilter())
74+
.map(exporterTestEnvironment -> exporterTestEnvironment.setBaseUrl(BASE_URL));
75+
}
76+
77+
@Verifyica.Prepare
78+
public static void prepare(ClassContext classContext) {
79+
TestSupport.getOrCreateNetwork(classContext);
80+
}
81+
82+
@Verifyica.BeforeAll
83+
public void beforeAll(ArgumentContext argumentContext) {
84+
Class<?> testClass = argumentContext.classContext().testClass();
85+
Network network = TestSupport.getOrCreateNetwork(argumentContext);
86+
TestSupport.initializeExporterTestEnvironment(argumentContext, network, testClass);
87+
}
88+
89+
private SSLContext initSSLContextForClientAuth(JmxExporterMode mode) {
90+
try {
91+
SSLContext sslContext = SSLContext.getInstance("TLS");
92+
93+
// to verify cert auth with existing test pki resources, use self-signed server cert as
94+
// client cert and source of trust
95+
final String type = "PKCS12";
96+
final char[] password = "changeit".toCharArray();
97+
final String keyStoreResource =
98+
Strings.formatIfArgs(
99+
"%s/%s/localhost.pkcs12",
100+
this.getClass().getSimpleName(), mode.toString());
101+
KeyStore keyStore = KeyStore.getInstance(type);
102+
try (InputStream inputStream = this.getClass().getResourceAsStream(keyStoreResource)) {
103+
keyStore.load(inputStream, password);
104+
}
105+
KeyManagerFactory km =
106+
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
107+
km.init(keyStore, password);
108+
TrustManagerFactory tm =
109+
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
110+
tm.init(keyStore);
111+
112+
sslContext.init(
113+
km.getKeyManagers(), tm.getTrustManagers(), new java.security.SecureRandom());
114+
115+
return sslContext;
116+
} catch (IOException
117+
| NoSuchAlgorithmException
118+
| KeyStoreException
119+
| CertificateException
120+
| KeyManagementException
121+
| UnrecoverableKeyException e) {
122+
e.printStackTrace();
123+
}
124+
return null;
125+
}
126+
127+
@Verifyica.Test
128+
@Verifyica.Order(1)
129+
public void testHealthy(ExporterTestEnvironment exporterTestEnvironment) throws IOException {
130+
131+
String url = exporterTestEnvironment.getUrl(ExporterPath.HEALTHY);
132+
133+
try {
134+
HttpResponse httpResponse = HttpClient.sendRequest(url);
135+
throw new AssertionFailedError("expected exception on no client cert");
136+
} catch (SocketException expectedOnNoClientCert) {
137+
}
138+
// set ssl context with client key store and verify all is good
139+
final SSLSocketFactory existing = HttpsURLConnection.getDefaultSSLSocketFactory();
140+
try {
141+
HttpsURLConnection.setDefaultSSLSocketFactory(
142+
initSSLContextForClientAuth(exporterTestEnvironment.getJmxExporterMode())
143+
.getSocketFactory());
144+
145+
HttpResponse httpResponse = HttpClient.sendRequest(url);
146+
147+
assertHealthyResponse(httpResponse);
148+
149+
} finally {
150+
HttpsURLConnection.setDefaultSSLSocketFactory(existing);
151+
}
152+
}
153+
154+
@Verifyica.AfterAll
155+
public void afterAll(ArgumentContext argumentContext) throws Throwable {
156+
List<Trap> traps = new ArrayList<>();
157+
158+
traps.add(new Trap(() -> TestSupport.destroyExporterTestEnvironment(argumentContext)));
159+
traps.add(new Trap(() -> TestSupport.destroyNetwork(argumentContext)));
160+
161+
Trap.assertEmpty(traps);
162+
}
163+
164+
@Verifyica.Conclude
165+
public static void conclude(ClassContext classContext) throws Throwable {
166+
new Trap(() -> TestSupport.destroyNetwork(classContext)).assertEmpty();
167+
}
168+
169+
private void assertMetricsResponse(
170+
ExporterTestEnvironment exporterTestEnvironment,
171+
HttpResponse httpResponse,
172+
MetricsContentType metricsContentType) {
173+
assertCommonMetricsResponse(httpResponse, metricsContentType);
174+
Map<String, Collection<Metric>> metrics = new LinkedHashMap<>();
175+
176+
// Validate no duplicate metrics (metrics with the same name and labels)
177+
// and build a Metrics Map for subsequent processing
178+
179+
Set<String> compositeSet = new LinkedHashSet<>();
180+
MetricsParser.parseCollection(httpResponse)
181+
.forEach(
182+
metric -> {
183+
String name = metric.name();
184+
Map<String, String> labels = metric.labels();
185+
String composite = name + " " + labels;
186+
assertThat(compositeSet).doesNotContain(composite);
187+
compositeSet.add(composite);
188+
metrics.computeIfAbsent(name, k -> new ArrayList<>()).add(metric);
189+
});
190+
191+
// Validate common / known metrics (and potentially values)
192+
193+
boolean isJmxExporterModeJavaAgent =
194+
exporterTestEnvironment.getJmxExporterMode() == JmxExporterMode.JavaAgent;
195+
196+
String buildInfoName =
197+
TestSupport.getBuildInfoName(exporterTestEnvironment.getJmxExporterMode());
198+
199+
assertMetric(metrics)
200+
.ofType(Metric.Type.GAUGE)
201+
.withName("jmx_exporter_build_info")
202+
.withLabel("name", buildInfoName)
203+
.withValue(1d)
204+
.isPresent();
205+
206+
assertMetric(metrics)
207+
.ofType(Metric.Type.GAUGE)
208+
.withName("jmx_scrape_error")
209+
.withValue(0d)
210+
.isPresent();
211+
212+
assertMetric(metrics)
213+
.ofType(Metric.Type.COUNTER)
214+
.withName("jmx_config_reload_success_total")
215+
.withValue(0d)
216+
.isPresent();
217+
218+
assertMetric(metrics)
219+
.ofType(Metric.Type.GAUGE)
220+
.withName("jvm_memory_used_bytes")
221+
.withLabel("area", "nonheap")
222+
.isPresentWhen(isJmxExporterModeJavaAgent);
223+
224+
assertMetric(metrics)
225+
.ofType(Metric.Type.GAUGE)
226+
.withName("jvm_memory_used_bytes")
227+
.withLabel("area", "heap")
228+
.isPresentWhen(isJmxExporterModeJavaAgent);
229+
230+
assertMetric(metrics)
231+
.ofType(Metric.Type.GAUGE)
232+
.withName("jvm_memory_used_bytes")
233+
.withLabel("area", "nonheap")
234+
.isPresentWhen(isJmxExporterModeJavaAgent);
235+
236+
assertMetric(metrics)
237+
.ofType(Metric.Type.GAUGE)
238+
.withName("jvm_memory_used_bytes")
239+
.withLabel("area", "heap")
240+
.isPresentWhen(isJmxExporterModeJavaAgent);
241+
242+
assertMetric(metrics)
243+
.ofType(Metric.Type.UNTYPED)
244+
.withName("io_prometheus_jmx_tabularData_Server_1_Disk_Usage_Table_size")
245+
.withLabel("source", "/dev/sda1")
246+
.withValue(7.516192768E9d)
247+
.isPresent();
248+
249+
assertMetric(metrics)
250+
.ofType(Metric.Type.UNTYPED)
251+
.withName("io_prometheus_jmx_tabularData_Server_2_Disk_Usage_Table_pcent")
252+
.withLabel("source", "/dev/sda2")
253+
.withValue(0.8d)
254+
.isPresent();
255+
256+
assertMetric(metrics)
257+
.ofType(Metric.Type.UNTYPED)
258+
.withName(
259+
"io_prometheus_jmx_test_PerformanceMetricsMBean_PerformanceMetrics_ActiveSessions")
260+
.withValue(2.0d)
261+
.isPresent();
262+
263+
assertMetric(metrics)
264+
.ofType(Metric.Type.UNTYPED)
265+
.withName(
266+
"io_prometheus_jmx_test_PerformanceMetricsMBean_PerformanceMetrics_Bootstraps")
267+
.withValue(4.0d)
268+
.isPresent();
269+
270+
assertMetric(metrics)
271+
.ofType(Metric.Type.UNTYPED)
272+
.withName(
273+
"io_prometheus_jmx_test_PerformanceMetricsMBean_PerformanceMetrics_BootstrapsDeferred")
274+
.withValue(6.0d)
275+
.isPresent();
276+
}
277+
}

integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/JavaAgent/exporter.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ httpServer:
44
maximum: 10
55
keepAliveTime: 120 # seconds
66
ssl:
7+
needClientAuth: false
78
keyStore:
89
filename: localhost.jks
10+
type: JKS
11+
password: changeit
12+
trustStore:
13+
filename: localhost.jks
14+
type: JKS
915
password: changeit
1016
certificate:
1117
alias: localhost

integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/Standalone/exporter.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ httpServer:
44
maximum: 10
55
keepAliveTime: 120 # seconds
66
ssl:
7+
needClientAuth: false
78
keyStore:
89
filename: localhost.jks
10+
type: JKS
11+
password: changeit
12+
trustStore:
13+
filename: localhost.jks
14+
type: JKS
915
password: changeit
1016
certificate:
1117
alias: localhost
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
3+
java \
4+
-Xmx512M \
5+
-javaagent:jmx_prometheus_javaagent.jar=8888:exporter.yaml \
6+
-jar jmx_example_application.jar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
httpServer:
2+
ssl:
3+
needClientAuth: true
4+
keyStore:
5+
type: PKCS12
6+
filename: localhost.pkcs12
7+
password: changeit
8+
trustStore:
9+
type: PKCS12
10+
filename: localhost.pkcs12
11+
password: changeit
12+
certificate:
13+
alias: localhost
14+
rules:
15+
- pattern: ".*"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
java \
4+
-Xmx512M \
5+
-Dcom.sun.management.jmxremote=true \
6+
-Dcom.sun.management.jmxremote.authenticate=false \
7+
-Dcom.sun.management.jmxremote.local.only=false \
8+
-Dcom.sun.management.jmxremote.port=9999 \
9+
-Dcom.sun.management.jmxremote.registry.ssl=false \
10+
-Dcom.sun.management.jmxremote.rmi.port=9999 \
11+
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false \
12+
-Dcom.sun.management.jmxremote.ssl=false \
13+
-jar jmx_example_application.jar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
java \
4+
-Xmx512M \
5+
-jar jmx_prometheus_standalone.jar 8888 exporter.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
httpServer:
2+
ssl:
3+
needClientAuth: true
4+
keyStore:
5+
type: PKCS12
6+
filename: localhost.pkcs12
7+
password: changeit
8+
trustStore:
9+
type: PKCS12
10+
filename: localhost.pkcs12
11+
password: changeit
12+
certificate:
13+
alias: localhost
14+
hostPort: application:9999
15+
rules:
16+
- pattern: ".*"

0 commit comments

Comments
 (0)