diff --git a/docs/content/1.1.0/http-mode/ssl.md b/docs/content/1.1.0/http-mode/ssl.md index 96342984..82f3c4b0 100644 --- a/docs/content/1.1.0/http-mode/ssl.md +++ b/docs/content/1.1.0/http-mode/ssl.md @@ -26,6 +26,24 @@ httpServer: 2. Create a keystore and add your certificate +If you need to verify a clients certificate, you set needClientAuth and configure the trustStore parameters + +```yaml +httpServer: + ssl: + needClientAuth: true + trustStore: + filename: ca.jks + type: JKS + password: changeit + keyStore: + filename: localhost.jks + password: changeit + certificate: + alias: localhost +``` + + ### Configuration (using System properties) 1. Add configuration to your exporter YAML file diff --git a/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth.java b/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth.java new file mode 100644 index 00000000..41dfd487 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2024-present The Prometheus jmx_exporter Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.prometheus.jmx.test.http.ssl; + +import static io.prometheus.jmx.test.support.Assertions.assertCommonMetricsResponse; +import static io.prometheus.jmx.test.support.Assertions.assertHealthyResponse; +import static io.prometheus.jmx.test.support.metrics.MetricAssertion.assertMetric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import io.prometheus.jmx.test.support.ExporterPath; +import io.prometheus.jmx.test.support.ExporterTestEnvironment; +import io.prometheus.jmx.test.support.JmxExporterMode; +import io.prometheus.jmx.test.support.PKCS12KeyStoreExporterTestEnvironmentFilter; +import io.prometheus.jmx.test.support.TestSupport; +import io.prometheus.jmx.test.support.http.HttpClient; +import io.prometheus.jmx.test.support.http.HttpResponse; +import io.prometheus.jmx.test.support.metrics.Metric; +import io.prometheus.jmx.test.support.metrics.MetricsContentType; +import io.prometheus.jmx.test.support.metrics.MetricsParser; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import org.assertj.core.util.Strings; +import org.testcontainers.containers.Network; +import org.verifyica.api.ArgumentContext; +import org.verifyica.api.ClassContext; +import org.verifyica.api.Trap; +import org.verifyica.api.Verifyica; + +public class SSLWithTrustStoreAndClientAuth { + + private static final String BASE_URL = "https://localhost"; + + @Verifyica.ArgumentSupplier() // not parallel as the static HttpsURLConnection + // defaultSSLSocketFactory is manipulated + public static Stream arguments() { + // Filter Java versions that don't support the PKCS12 keystore + // format or don't support the required TLS cipher suites + return ExporterTestEnvironment.createExporterTestEnvironments() + .filter(new PKCS12KeyStoreExporterTestEnvironmentFilter()) + .map(exporterTestEnvironment -> exporterTestEnvironment.setBaseUrl(BASE_URL)); + } + + @Verifyica.Prepare + public static void prepare(ClassContext classContext) { + TestSupport.getOrCreateNetwork(classContext); + } + + @Verifyica.BeforeAll + public void beforeAll(ArgumentContext argumentContext) { + Class testClass = argumentContext.classContext().testClass(); + Network network = TestSupport.getOrCreateNetwork(argumentContext); + TestSupport.initializeExporterTestEnvironment(argumentContext, network, testClass); + } + + private SSLContext initSSLContextForClientAuth(JmxExporterMode mode) throws Exception { + SSLContext sslContext = SSLContext.getInstance("TLS"); + + // to verify cert auth with existing test pki resources, use self-signed server cert as + // client cert and source of trust + final String type = "PKCS12"; + final char[] password = "changeit".toCharArray(); + final String keyStoreResource = + Strings.formatIfArgs( + "%s/%s/localhost.pkcs12", this.getClass().getSimpleName(), mode.toString()); + KeyStore keyStore = KeyStore.getInstance(type); + try (InputStream inputStream = this.getClass().getResourceAsStream(keyStoreResource)) { + keyStore.load(inputStream, password); + } + KeyManagerFactory km = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + km.init(keyStore, password); + TrustManagerFactory tm = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tm.init(keyStore); + + sslContext.init( + km.getKeyManagers(), tm.getTrustManagers(), new java.security.SecureRandom()); + + return sslContext; + } + + @Verifyica.Test + @Verifyica.Order(1) + public void testHealthy(ExporterTestEnvironment exporterTestEnvironment) throws Exception { + + String url = exporterTestEnvironment.getUrl(ExporterPath.HEALTHY); + + assertThatExceptionOfType(IOException.class) + .isThrownBy( + () -> { + HttpClient.sendRequest(url); + }); + + // set ssl context with client key store and verify all is good + final SSLSocketFactory existing = HttpsURLConnection.getDefaultSSLSocketFactory(); + try { + HttpsURLConnection.setDefaultSSLSocketFactory( + initSSLContextForClientAuth(exporterTestEnvironment.getJmxExporterMode()) + .getSocketFactory()); + + HttpResponse httpResponse = HttpClient.sendRequest(url); + + assertHealthyResponse(httpResponse); + + } finally { + HttpsURLConnection.setDefaultSSLSocketFactory(existing); + } + } + + @Verifyica.AfterAll + public void afterAll(ArgumentContext argumentContext) throws Throwable { + List traps = new ArrayList<>(); + + traps.add(new Trap(() -> TestSupport.destroyExporterTestEnvironment(argumentContext))); + traps.add(new Trap(() -> TestSupport.destroyNetwork(argumentContext))); + + Trap.assertEmpty(traps); + } + + @Verifyica.Conclude + public static void conclude(ClassContext classContext) throws Throwable { + new Trap(() -> TestSupport.destroyNetwork(classContext)).assertEmpty(); + } + + private void assertMetricsResponse( + ExporterTestEnvironment exporterTestEnvironment, + HttpResponse httpResponse, + MetricsContentType metricsContentType) { + assertCommonMetricsResponse(httpResponse, metricsContentType); + Map> metrics = new LinkedHashMap<>(); + + // Validate no duplicate metrics (metrics with the same name and labels) + // and build a Metrics Map for subsequent processing + + Set compositeSet = new LinkedHashSet<>(); + MetricsParser.parseCollection(httpResponse) + .forEach( + metric -> { + String name = metric.name(); + Map labels = metric.labels(); + String composite = name + " " + labels; + assertThat(compositeSet).doesNotContain(composite); + compositeSet.add(composite); + metrics.computeIfAbsent(name, k -> new ArrayList<>()).add(metric); + }); + + // Validate common / known metrics (and potentially values) + + boolean isJmxExporterModeJavaAgent = + exporterTestEnvironment.getJmxExporterMode() == JmxExporterMode.JavaAgent; + + String buildInfoName = + TestSupport.getBuildInfoName(exporterTestEnvironment.getJmxExporterMode()); + + assertMetric(metrics) + .ofType(Metric.Type.GAUGE) + .withName("jmx_exporter_build_info") + .withLabel("name", buildInfoName) + .withValue(1d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.GAUGE) + .withName("jmx_scrape_error") + .withValue(0d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.COUNTER) + .withName("jmx_config_reload_success_total") + .withValue(0d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.GAUGE) + .withName("jvm_memory_used_bytes") + .withLabel("area", "nonheap") + .isPresentWhen(isJmxExporterModeJavaAgent); + + assertMetric(metrics) + .ofType(Metric.Type.GAUGE) + .withName("jvm_memory_used_bytes") + .withLabel("area", "heap") + .isPresentWhen(isJmxExporterModeJavaAgent); + + assertMetric(metrics) + .ofType(Metric.Type.GAUGE) + .withName("jvm_memory_used_bytes") + .withLabel("area", "nonheap") + .isPresentWhen(isJmxExporterModeJavaAgent); + + assertMetric(metrics) + .ofType(Metric.Type.GAUGE) + .withName("jvm_memory_used_bytes") + .withLabel("area", "heap") + .isPresentWhen(isJmxExporterModeJavaAgent); + + assertMetric(metrics) + .ofType(Metric.Type.UNTYPED) + .withName("io_prometheus_jmx_tabularData_Server_1_Disk_Usage_Table_size") + .withLabel("source", "/dev/sda1") + .withValue(7.516192768E9d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.UNTYPED) + .withName("io_prometheus_jmx_tabularData_Server_2_Disk_Usage_Table_pcent") + .withLabel("source", "/dev/sda2") + .withValue(0.8d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.UNTYPED) + .withName( + "io_prometheus_jmx_test_PerformanceMetricsMBean_PerformanceMetrics_ActiveSessions") + .withValue(2.0d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.UNTYPED) + .withName( + "io_prometheus_jmx_test_PerformanceMetricsMBean_PerformanceMetrics_Bootstraps") + .withValue(4.0d) + .isPresent(); + + assertMetric(metrics) + .ofType(Metric.Type.UNTYPED) + .withName( + "io_prometheus_jmx_test_PerformanceMetricsMBean_PerformanceMetrics_BootstrapsDeferred") + .withValue(6.0d) + .isPresent(); + } +} diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/JavaAgent/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/JavaAgent/exporter.yaml index b979e58c..f8a074de 100644 --- a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/JavaAgent/exporter.yaml +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/JavaAgent/exporter.yaml @@ -4,8 +4,14 @@ httpServer: maximum: 10 keepAliveTime: 120 # seconds ssl: + needClientAuth: false keyStore: filename: localhost.jks + type: JKS + password: changeit + trustStore: + filename: localhost.jks + type: JKS password: changeit certificate: alias: localhost diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/Standalone/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/Standalone/exporter.yaml index aa73cb93..b4ce2380 100644 --- a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/Standalone/exporter.yaml +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/CompleteHttpServerConfigurationTest/Standalone/exporter.yaml @@ -4,8 +4,14 @@ httpServer: maximum: 10 keepAliveTime: 120 # seconds ssl: + needClientAuth: false keyStore: filename: localhost.jks + type: JKS + password: changeit + trustStore: + filename: localhost.jks + type: JKS password: changeit certificate: alias: localhost diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/application.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/application.sh new file mode 100755 index 00000000..5795d5c4 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/application.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -javaagent:jmx_prometheus_javaagent.jar=8888:exporter.yaml \ + -jar jmx_example_application.jar diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/exporter.yaml new file mode 100644 index 00000000..c26a2bc8 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/exporter.yaml @@ -0,0 +1,15 @@ +httpServer: + ssl: + needClientAuth: true + keyStore: + type: PKCS12 + filename: localhost.pkcs12 + password: changeit + trustStore: + type: PKCS12 + filename: localhost.pkcs12 + password: changeit + certificate: + alias: localhost +rules: + - pattern: ".*" diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/localhost.pkcs12 b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/localhost.pkcs12 new file mode 100644 index 00000000..84a3c893 Binary files /dev/null and b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/JavaAgent/localhost.pkcs12 differ diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/application.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/application.sh new file mode 100755 index 00000000..9efc7364 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/application.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -Dcom.sun.management.jmxremote=true \ + -Dcom.sun.management.jmxremote.authenticate=false \ + -Dcom.sun.management.jmxremote.local.only=false \ + -Dcom.sun.management.jmxremote.port=9999 \ + -Dcom.sun.management.jmxremote.registry.ssl=false \ + -Dcom.sun.management.jmxremote.rmi.port=9999 \ + -Dcom.sun.management.jmxremote.ssl.need.client.auth=false \ + -Dcom.sun.management.jmxremote.ssl=false \ + -jar jmx_example_application.jar diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/exporter.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/exporter.sh new file mode 100755 index 00000000..3de46adb --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/exporter.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -jar jmx_prometheus_standalone.jar 8888 exporter.yaml diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/exporter.yaml new file mode 100644 index 00000000..2d3e7c19 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/exporter.yaml @@ -0,0 +1,16 @@ +httpServer: + ssl: + needClientAuth: true + keyStore: + type: PKCS12 + filename: localhost.pkcs12 + password: changeit + trustStore: + type: PKCS12 + filename: localhost.pkcs12 + password: changeit + certificate: + alias: localhost +hostPort: application:9999 +rules: + - pattern: ".*" diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/localhost.pkcs12 b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/localhost.pkcs12 new file mode 100644 index 00000000..84a3c893 Binary files /dev/null and b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/ssl/SSLWithTrustStoreAndClientAuth/Standalone/localhost.pkcs12 differ diff --git a/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java b/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java index 2918a24e..fe074fe0 100644 --- a/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java +++ b/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java @@ -20,6 +20,7 @@ import com.sun.net.httpserver.Authenticator; import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsParameters; import io.prometheus.jmx.common.configuration.ConvertToInteger; import io.prometheus.jmx.common.configuration.ConvertToMapAccessor; import io.prometheus.jmx.common.configuration.ConvertToString; @@ -38,6 +39,7 @@ import java.io.Reader; import java.net.InetAddress; import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -50,6 +52,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLParameters; import org.yaml.snakeyaml.Yaml; /** @@ -68,6 +71,10 @@ public class HTTPServerFactory { private static final Map PBKDF2_ALGORITHM_ITERATIONS; private static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; private static final String JAVAX_NET_SSL_KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; + private static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore"; + private static final String JAVAX_NET_SSL_TRUST_STORE_TYPE = "javax.net.ssl.trustStoreType"; + private static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = + "javax.net.ssl.trustStorePassword"; private static final int PBKDF2_KEY_LENGTH_BITS = 128; @@ -632,6 +639,23 @@ public void configureSSL(HTTPServer.Builder httpServerBuilder) { + " must not be blank"))) .orElse(System.getProperty(JAVAX_NET_SSL_KEY_STORE)); + String keyStoreType = + rootYamlMapAccessor + .get("/httpServer/ssl/keyStore/type") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/keyStore/type" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/keyStore/type" + + " must not be blank"))) + .orElse(KeyStore.getDefaultType()); + String keyStorePassword = rootYamlMapAccessor .get("/httpServer/ssl/keyStore/password") @@ -669,10 +693,102 @@ public void configureSSL(HTTPServer.Builder httpServerBuilder) { "/httpServer/ssl/certificate/alias is a required" + " string")); + String needClientAuthAttributeVal = null; + String trustStoreFilename = null; + String trustStoreType = null; + String trustStorePassword = null; + Optional needClientAuthAttribute = + rootYamlMapAccessor.get("/httpServer/ssl/needClientAuth"); + if (needClientAuthAttribute.isPresent()) { + needClientAuthAttributeVal = + rootYamlMapAccessor + .get("/httpServer/ssl/needClientAuth") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/needClientAuth" + + " it must be the string form of a" + + " boolean"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/needClientAuth" + + " it must not be blank"))) + .get(); + + trustStoreFilename = + rootYamlMapAccessor + .get("/httpServer/ssl/trustStore/filename") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/trustStore/filename" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/trustStore/filename" + + " must not be blank"))) + .orElse(System.getProperty(JAVAX_NET_SSL_TRUST_STORE)); + + trustStoreType = + rootYamlMapAccessor + .get("/httpServer/ssl/trustStore/type") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/trustStore/type" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/trustStore/type" + + " must not be blank"))) + .orElse(System.getProperty(JAVAX_NET_SSL_TRUST_STORE_TYPE)); + + trustStorePassword = + rootYamlMapAccessor + .get("/httpServer/ssl/trustStore/password") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/trustStore/password" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/ssl/trustStore/password" + + " must not be blank"))) + .orElse(System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD)); + } + final boolean needClientAuth = Boolean.valueOf(needClientAuthAttributeVal); + httpServerBuilder.httpsConfigurator( new HttpsConfigurator( SSLContextFactory.createSSLContext( - keyStoreFilename, keyStorePassword, certificateAlias))); + keyStoreType, + keyStoreFilename, + keyStorePassword, + certificateAlias, + trustStoreType, + trustStoreFilename, + trustStorePassword)) { + @Override + public void configure(HttpsParameters params) { + SSLParameters sslParameters = + getSSLContext().getDefaultSSLParameters(); + sslParameters.setNeedClientAuth(needClientAuth); + params.setSSLParameters(sslParameters); + } + }); } catch (GeneralSecurityException | IOException e) { String message = e.getMessage(); if (message != null && !message.trim().isEmpty()) { diff --git a/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/ssl/SSLContextFactory.java b/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/ssl/SSLContextFactory.java index a948b1c5..bf8964c1 100644 --- a/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/ssl/SSLContextFactory.java +++ b/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/ssl/SSLContextFactory.java @@ -45,21 +45,33 @@ private SSLContextFactory() { /** * Method to create an SSLContext * + * @param keyStoreType keyStoreType * @param keyStoreFilename keyStoreFilename * @param keyStorePassword keyStorePassword * @param certificateAlias certificateAlias + * @param trustStoreType trustStoreType + * @param trustStoreFilename trustStoreFilename + * @param trustStorePassword trustStorePassword * @return the return value * @throws GeneralSecurityException GeneralSecurityException * @throws IOException IOException */ public static SSLContext createSSLContext( - String keyStoreFilename, String keyStorePassword, String certificateAlias) + String keyStoreType, + String keyStoreFilename, + String keyStorePassword, + String certificateAlias, + String trustStoreType, + String trustStoreFilename, + String trustStorePassword) throws GeneralSecurityException, IOException { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + KeyStore trustStore = null; try (InputStream inputStream = Files.newInputStream(Paths.get(keyStoreFilename))) { // Load the keystore - keyStore.load(inputStream, keyStorePassword.toCharArray()); + keyStore.load( + inputStream, keyStorePassword != null ? keyStorePassword.toCharArray() : null); // Loop through the certificate aliases in the keystore // building a set of certificate aliases that don't match @@ -86,28 +98,38 @@ public static SSLContext createSSLContext( "certificate alias [%s] not found in keystore [%s]", certificateAlias, keyStoreFilename)); } + } - // Create and initialize an SSLContext + if (trustStoreFilename != null) { + trustStore = KeyStore.getInstance(trustStoreType); + try (InputStream inputStream = Files.newInputStream(Paths.get(trustStoreFilename))) { + trustStore.load( + inputStream, + trustStorePassword != null ? trustStorePassword.toCharArray() : null); + } + } - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + // Create and initialize an SSLContext - keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init( + keyStore, keyStorePassword != null ? keyStorePassword.toCharArray() : null); - trustManagerFactory.init(keyStore); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - SSLContext sslContext = createSSLContext(); + trustManagerFactory.init(trustStore == null ? keyStore : trustStore); - sslContext.init( - keyManagerFactory.getKeyManagers(), - trustManagerFactory.getTrustManagers(), - new SecureRandom()); + SSLContext sslContext = createSSLContext(); - return sslContext; - } + sslContext.init( + keyManagerFactory.getKeyManagers(), + trustManagerFactory.getTrustManagers(), + new SecureRandom()); + + return sslContext; } /**