This is an automated email from the ASF dual-hosted git repository.
andor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push:
new 9d23039612 ZOOKEEPER-5024: Allow to set TLS version and ciphers for
PrometheusMetricsProvider
9d23039612 is described below
commit 9d23039612ebb60ef49506898c3ed6e63bb55fdc
Author: Dávid Paksy <[email protected]>
AuthorDate: Tue Apr 7 22:23:54 2026 +0200
ZOOKEEPER-5024: Allow to set TLS version and ciphers for
PrometheusMetricsProvider
Reviewers: meszibalu, anmolnar
Author: PDavid
Closes #2360 from PDavid/ZOOKEEPER-5024-prometheus-https-ciphers-protocols
---
.../src/main/resources/markdown/zookeeperAdmin.md | 59 ++++-
.../main/resources/markdown/zookeeperMonitor.md | 18 ++
.../prometheus/PrometheusMetricsProvider.java | 31 ++-
.../PrometheusMetricsProviderConfigTest.java | 285 +++++++++++++++++----
4 files changed, 337 insertions(+), 56 deletions(-)
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index 786aa17c07..c87ce053e7 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -2286,8 +2286,17 @@ options are used to configure the
[AdminServer](#sc_adminserver).
**New in 3.8.0:** Prometheus.io exporter will start a Jetty server and
listen this address, default is "0.0.0.0"
* *metricsProvider.httpPort* :
- Prometheus.io exporter will start a Jetty server and bind to this port, it
defaults to 7000.
- Prometheus end point will be http://hostname:httPort/metrics.
+ Prometheus.io exporter will start a Jetty server and bind to this port.
+ Prometheus end point will be `http://hostname:httpPort/metrics`.
+ If omitted no HTTP port will be opened.
+ * Note: Either HTTP or HTTPS port has to be specified or both.
+
+* *metricsProvider.httpsPort* :
+ **New in 3.10.0:**
+ Prometheus.io exporter will start a Jetty server and bind to this port.
+ Prometheus end point will be `https://hostname:httpsPort/metrics`.
+ If omitted no HTTPS port will be opened.
+ * Note: Either HTTP or HTTPS port has to be specified or both.
* *metricsProvider.exportJvmInfo* :
If this property is set to **true** Prometheus.io will export useful
metrics about the JVM.
@@ -2308,7 +2317,51 @@ options are used to configure the
[AdminServer](#sc_adminserver).
**New in 3.7.1:**
The timeout in ms for Prometheus worker threads shutdown.
Default value is 1000ms.
-
+
+* *metricsProvider.ssl.keyStore.location* and
*metricsProvider.ssl.keyStore.password*:
+ **New in 3.10.0:**
+ Specifies the file path to a Java keystore containing the local
+ credentials to be used for PrometheusMetricsProvider TLS connections and the
+ password to unlock the file.
+
+* *metricsProvider.ssl.keyStore.type*:
+ **New in 3.10.0:**
+ Specifies the file format of the PrometheusMetricsProvider keystore.
Values: JKS, PEM, PKCS12 or null (detect by filename).
+ Default: null.
+
+* *metricsProvider.ssl.trustStore.location* and
*metricsProvider.ssl.trustStore.password*:
+ **New in 3.10.0:**
+ Specifies the file path to a Java truststore containing the remote
+ credentials to be used for PrometheusMetricsProvider TLS connections and the
+ password to unlock the file.
+
+* *metricsProvider.ssl.trustStore.type*:
+ **New in 3.10.0:**
+ Specifies the file format of the PrometheusMetricsProvider truststore.
Values: JKS, PEM, PKCS12 or null (detect by filename).
+ Default: null.
+
+* *metricsProvider.ssl.need.client.auth*:
+ **New in 3.10.0:**
+ Specifies options to authenticate SSL connections from clients.
+ When set to true, PrometheusMetricsProvider will "require" client
authentication.
+ Default: true
+
+* *metricsProvider.ssl.want.client.auth*:
+ **New in 3.10.0:**
+ Specifies options to authenticate SSL connections from clients.
+ When set to true, PrometheusMetricsProvider will "request" client
authentication.
+ Default: true
+
+* *metricsProvider.ssl.ciphersuites* :
+ **New in 3.10.0:**
+ The enabled cipher suites to be used in TLS negotiation for
PrometheusMetricsProvider.
+ Default value is Jetty default.
+
+* *metricsProvider.ssl.enabledProtocols* :
+ **New in 3.10.0:**
+ The enabled protocols to be used in TLS negotiation for
PrometheusMetricsProvider.
+ Default value is Jetty default.
+
<a name="Communication+using+the+Netty+framework"></a>
### Communication using the Netty framework
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md
b/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md
index 180f1273c1..705b5eb049 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md
@@ -81,6 +81,24 @@ ZooKeeper also supports SSL for Prometheus metrics, which
provides secure data t
metricsProvider.httpPort=7000
metricsProvider.httpsPort=4443
```
+
+#### Configure TLS protocols and cipher suites for SSL/TLS negotiation in
Prometheus Metrics:
+
+It is also possible to restrict TLS versions and cipher suites for
PrometheusMetricsProvider.
+Add the following configuration settings to the `zoo.cfg` config file:
+
+```
+metricsProvider.ssl.enabledProtocols=TLSv1.2,TLSv1.3
+metricsProvider.ssl.ciphersuites=TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+```
+
+To verify raise the log level of PrometheusMetricsProvider to DEBUG and check
that the following entries can be seen in the logs:
+
+```
+2026-03-11 15:46:36,997 [myid:] - INFO
[main:o.a.z.m.p.PrometheusMetricsProvider@245] - Setting enabled protocols:
'TLSv1.2,TLSv1.3'
+2026-03-11 15:46:36,997 [myid:] - INFO
[main:o.a.z.m.p.PrometheusMetricsProvider@251] - Setting enabled cipherSuites:
'TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'
+```
+
### Prometheus
- Running a [Prometheus](https://prometheus.io/) monitoring service is the
easiest way to ingest and record ZooKeeper's metrics.
diff --git
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
index 1fbb34169c..8ae6178e0f 100644
---
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
+++
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java
@@ -37,7 +37,6 @@
import org.apache.zookeeper.metrics.Gauge;
import org.apache.zookeeper.metrics.GaugeSet;
import org.apache.zookeeper.metrics.MetricsContext;
-import org.apache.zookeeper.metrics.MetricsContext.DetailLevel;
import org.apache.zookeeper.metrics.MetricsProvider;
import org.apache.zookeeper.metrics.MetricsProviderLifeCycleException;
import org.apache.zookeeper.metrics.Summary;
@@ -83,8 +82,14 @@ public class PrometheusMetricsProvider implements
MetricsProvider {
private String trustStoreType;
private boolean needClientAuth = true; // Secure default
private boolean wantClientAuth = true; // Secure default
+ private String enabledProtocols;
+ private String cipherSuites;
// Constants for configuration
+ public static final String HTTP_HOST = "httpHost";
+ public static final String HTTP_PORT = "httpPort";
+ public static final String EXPORT_JVM_INFO = "exportJvmInfo";
+ public static final String HTTPS_PORT = "httpsPort";
public static final String NUM_WORKER_THREADS = "numWorkerThreads";
public static final String SSL_KEYSTORE_LOCATION = "ssl.keyStore.location";
public static final String SSL_KEYSTORE_PASSWORD = "ssl.keyStore.password";
@@ -94,6 +99,8 @@ public class PrometheusMetricsProvider implements
MetricsProvider {
public static final String SSL_TRUSTSTORE_TYPE = "ssl.trustStore.type";
public static final String SSL_NEED_CLIENT_AUTH = "ssl.need.client.auth";
public static final String SSL_WANT_CLIENT_AUTH = "ssl.want.client.auth";
+ public static final String SSL_ENABLED_PROTOCOLS = "ssl.enabledProtocols";
+ public static final String SSL_ENABLED_CIPHERS = "ssl.ciphersuites";
public static final int SCAN_INTERVAL = 60 * 10; // 10 minutes
/**
@@ -114,10 +121,10 @@ protected void doTrace(HttpServletRequest req,
HttpServletResponse resp) throws
public void configure(Properties configuration) throws
MetricsProviderLifeCycleException {
LOG.info("Initializing Prometheus metrics with Jetty, configuration:
{}", configuration);
- this.host = configuration.getProperty("httpHost", "0.0.0.0");
- this.httpPort = Integer.parseInt(configuration.getProperty("httpPort",
"-1"));
- this.httpsPort =
Integer.parseInt(configuration.getProperty("httpsPort", "-1"));
- this.exportJvmInfo =
Boolean.parseBoolean(configuration.getProperty("exportJvmInfo", "true"));
+ this.host = configuration.getProperty(HTTP_HOST, "0.0.0.0");
+ this.httpPort = Integer.parseInt(configuration.getProperty(HTTP_PORT,
"-1"));
+ this.httpsPort =
Integer.parseInt(configuration.getProperty(HTTPS_PORT, "-1"));
+ this.exportJvmInfo =
Boolean.parseBoolean(configuration.getProperty(EXPORT_JVM_INFO, "true"));
this.numWorkerThreads =
Integer.parseInt(configuration.getProperty(NUM_WORKER_THREADS, "10"));
// If httpsPort is specified, parse all SSL properties
@@ -130,6 +137,8 @@ public void configure(Properties configuration) throws
MetricsProviderLifeCycleE
this.trustStoreType =
configuration.getProperty(SSL_TRUSTSTORE_TYPE, "PKCS12");
this.needClientAuth =
Boolean.parseBoolean(configuration.getProperty(SSL_NEED_CLIENT_AUTH, "true"));
this.wantClientAuth =
Boolean.parseBoolean(configuration.getProperty(SSL_WANT_CLIENT_AUTH, "true"));
+ this.enabledProtocols =
configuration.getProperty(SSL_ENABLED_PROTOCOLS);
+ this.cipherSuites = configuration.getProperty(SSL_ENABLED_CIPHERS);
}
// Validate that at least one port is configured.
@@ -232,6 +241,18 @@ private SslContextFactory.Server createSslContextFactory()
{
sslContextFactory.setNeedClientAuth(this.needClientAuth);
sslContextFactory.setWantClientAuth(this.wantClientAuth);
+ if (enabledProtocols != null) {
+ LOG.debug("Setting enabled protocols: '{}'", enabledProtocols);
+ String[] enabledProtocolsArray = enabledProtocols.split(",");
+ sslContextFactory.setIncludeProtocols(enabledProtocolsArray);
+ }
+
+ if (cipherSuites != null) {
+ LOG.debug("Setting enabled cipherSuites: '{}'", cipherSuites);
+ String[] cipherSuitesArray = cipherSuites.split(",");
+ sslContextFactory.setIncludeCipherSuites(cipherSuitesArray);
+ }
+
return sslContextFactory;
}
diff --git
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
index c151854b05..bb24ab75d2 100644
---
a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
+++
b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java
@@ -18,89 +18,278 @@
package org.apache.zookeeper.metrics.prometheus;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
import java.util.Properties;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
import org.apache.zookeeper.metrics.MetricsProviderLifeCycleException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class PrometheusMetricsProviderConfigTest extends
PrometheusMetricsTestBase {
+ private static final String KEYSTORE_TYPE_JKS = "JKS";
+ private static final String PASSWORD = "testpass";
+
+ private String keyStorePath;
+ private String trustStorePath;
+ private PrometheusMetricsProvider provider;
+
+ @BeforeEach
+ public void setup() {
+ String testDataPath = System.getProperty("test.data.dir",
"src/test/resources/data");
+ keyStorePath = testDataPath + "/ssl/server_keystore.jks";
+ trustStorePath = testDataPath + "/ssl/server_truststore.jks";
+ }
+
+ @AfterEach
+ public void tearDown() {
+ if (provider != null) {
+ provider.stop();
+ }
+ }
+
@Test
- public void testInvalidPort() {
- assertThrows(MetricsProviderLifeCycleException.class, () -> {
- PrometheusMetricsProvider provider = new
PrometheusMetricsProvider();
- Properties configuration = new Properties();
- configuration.setProperty("httpPort", "65536");
- configuration.setProperty("exportJvmInfo", "false");
- provider.configure(configuration);
- provider.start();
- });
+ public void testInvalidPort() throws MetricsProviderLifeCycleException {
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT,
"65536");
+ configuration.setProperty(PrometheusMetricsProvider.EXPORT_JVM_INFO,
"false");
+ provider.configure(configuration);
+
+ MetricsProviderLifeCycleException exception =
+ assertThrows(MetricsProviderLifeCycleException.class,
provider::start);
+
+ assertEquals("Failed to start Prometheus Jetty server",
exception.getMessage());
+ assertNotNull(exception.getCause());
+ assertEquals("port out of range:65536",
exception.getCause().getMessage());
}
@Test
- public void testInvalidAddr() {
- assertThrows(MetricsProviderLifeCycleException.class, () -> {
- PrometheusMetricsProvider provider = new
PrometheusMetricsProvider();
- Properties configuration = new Properties();
- configuration.setProperty("httpHost", "master");
- provider.configure(configuration);
- provider.start();
- });
+ public void testNoPortSet() {
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ // Do not set HTTP port and HTTPS port here.
+
+ MetricsProviderLifeCycleException exception =
+ assertThrows(MetricsProviderLifeCycleException.class, () ->
provider.configure(configuration));
+
+ assertEquals("Either httpPort or httpsPort must be configured for
Prometheus exporter.",
+ exception.getMessage());
+ }
+
+ @Test
+ public void testInvalidAddr() throws MetricsProviderLifeCycleException {
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST,
"master");
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "0");
+ provider.configure(configuration);
+
+ MetricsProviderLifeCycleException exception =
+ assertThrows(MetricsProviderLifeCycleException.class,
provider::start);
+
+ assertEquals("Failed to start Prometheus Jetty server",
exception.getMessage());
+ assertNotNull(exception.getCause());
+ assertEquals("Failed to bind to master:0",
exception.getCause().getMessage());
}
@Test
public void testValidConfig() throws MetricsProviderLifeCycleException {
- PrometheusMetricsProvider provider = new PrometheusMetricsProvider();
+ provider = new PrometheusMetricsProvider();
Properties configuration = new Properties();
- configuration.setProperty("httpHost", "0.0.0.0");
- configuration.setProperty("httpPort", "0");
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST,
"0.0.0.0");
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "0");
provider.configure(configuration);
provider.start();
}
@Test
public void testValidSslConfig() throws MetricsProviderLifeCycleException {
- PrometheusMetricsProvider provider = new PrometheusMetricsProvider();
+ provider = new PrometheusMetricsProvider();
Properties configuration = new Properties();
- String testDataPath = System.getProperty("test.data.dir",
"src/test/resources/data");
- configuration.setProperty("httpHost", "127.0.0.1");
- configuration.setProperty("httpsPort", "0");
- configuration.setProperty("ssl.keyStore.location", testDataPath +
"/ssl/server_keystore.jks");
- configuration.setProperty("ssl.keyStore.password", "testpass");
- configuration.setProperty("ssl.trustStore.location", testDataPath +
"/ssl/server_truststore.jks");
- configuration.setProperty("ssl.trustStore.password", "testpass");
+
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST,
"127.0.0.1");
+ configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "0");
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION,
keyStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION,
trustStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD,
PASSWORD);
provider.configure(configuration);
provider.start();
}
@Test
public void testValidHttpsAndHttpConfig() throws
MetricsProviderLifeCycleException {
- PrometheusMetricsProvider provider = new PrometheusMetricsProvider();
+ provider = new PrometheusMetricsProvider();
Properties configuration = new Properties();
- String testDataPath = System.getProperty("test.data.dir",
"src/test/resources/data");
- configuration.setProperty("httpPort", "0");
- configuration.setProperty("httpsPort", "0");
- configuration.setProperty("ssl.keyStore.location", testDataPath +
"/ssl/server_keystore.jks");
- configuration.setProperty("ssl.keyStore.password", "testpass");
- configuration.setProperty("ssl.trustStore.location", testDataPath +
"/ssl/server_truststore.jks");
- configuration.setProperty("ssl.trustStore.password", "testpass");
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "0");
+ configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "0");
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION,
keyStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION,
trustStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD,
PASSWORD);
provider.configure(configuration);
provider.start();
}
@Test
public void testInvalidSslConfig() throws
MetricsProviderLifeCycleException {
- assertThrows(MetricsProviderLifeCycleException.class, () -> {
- PrometheusMetricsProvider provider = new
PrometheusMetricsProvider();
- Properties configuration = new Properties();
- String testDataPath = System.getProperty("test.data.dir",
"src/test/resources/data");
- configuration.setProperty("httpsPort", "50514");
- // keystore missing
- configuration.setProperty("ssl.keyStore.password", "testpass");
- configuration.setProperty("ssl.trustStore.location", testDataPath
+ "/ssl/server_truststore.jks");
- configuration.setProperty("ssl.trustStore.password", "testpass");
- provider.configure(configuration);
- provider.start();
- });
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT,
"50514");
+ // keystore missing
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION,
trustStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD,
PASSWORD);
+ provider.configure(configuration);
+
+ MetricsProviderLifeCycleException exception =
+ assertThrows(MetricsProviderLifeCycleException.class,
provider::start);
+
+ assertEquals("Failed to start Prometheus Jetty server",
exception.getMessage());
+ assertNotNull(exception.getCause());
+ assertEquals(
+ "SSL/TLS is enabled, but 'ssl.keyStore.location' is not set.",
+ exception.getCause().getMessage());
+ }
+
+ @Test
+ public void testHandshakeWithSupportedProtocol() throws Exception {
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST,
"127.0.0.1");
+ configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT,
"7000");
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION,
keyStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION,
trustStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_ENABLED_PROTOCOLS,
"TLSv1.3");
+ provider.configure(configuration);
+ provider.start();
+
+ // Use a raw SSLSocket to verify the handshake
+ SSLContext sslContext = createSSLContext(keyStorePath,
PASSWORD.toCharArray(), "TLSv1.3");
+ SSLSocketFactory factory = sslContext.getSocketFactory();
+
+ try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost",
7000)) {
+ socket.startHandshake();
+ String negotiatedProtocol = socket.getSession().getProtocol();
+
+ // Verify that we actually landed on the protocol we expected
+ assertEquals("TLSv1.3", negotiatedProtocol,
+ "The negotiated protocol should be TLSv1.3.");
+ }
+ }
+
+ @Test
+ public void testHandshakeWithUnsupportedProtocolFails() throws Exception {
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST,
"127.0.0.1");
+ configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT,
"7000");
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION,
keyStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION,
trustStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_ENABLED_PROTOCOLS,
"TLSv1.3");
+ provider.configure(configuration);
+ provider.start();
+
+ SSLContext sslContext = createSSLContext(keyStorePath,
PASSWORD.toCharArray(), "TLSv1.1");
+ SSLSocketFactory factory = sslContext.getSocketFactory();
+
+ try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost",
7000)) {
+ SSLHandshakeException exception =
assertThrows(SSLHandshakeException.class, socket::startHandshake);
+ assertEquals(
+ "No appropriate protocol (protocol is disabled or cipher
suites are inappropriate)",
+ exception.getMessage(),
+ "The handshake should have failed due to a protocol
mismatch.");
+ }
+ }
+
+ @Test
+ public void testCipherMismatchFails() throws Exception {
+ provider = new PrometheusMetricsProvider();
+ Properties configuration = new Properties();
+ configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST,
"127.0.0.1");
+ configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT,
"7000");
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION,
keyStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD,
PASSWORD);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION,
trustStorePath);
+
configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD,
PASSWORD);
+ System.setProperty(PrometheusMetricsProvider.SSL_ENABLED_CIPHERS,
+ "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384");
+ provider.configure(configuration);
+ provider.start();
+
+ SSLContext sslContext = createSSLContext(keyStorePath,
PASSWORD.toCharArray(), "TLSv1.2");
+ SSLSocketFactory factory = sslContext.getSocketFactory();
+
+ try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost",
7000)) {
+ // Force the client to use a cipher NOT enabled for the AdminServer
+ String[] unsupportedCiphers = new
String[]{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"};
+ socket.setEnabledCipherSuites(unsupportedCiphers);
+
+ assertThrows(SSLHandshakeException.class, socket::startHandshake,
+ "The handshake should have failed due to a cipher
mismatch.");
+ }
+ }
+
+ private SSLContext createSSLContext(String keystorePath, char[] password,
String protocol)
+ throws Exception {
+ KeyManager[] keyManagers = getKeyManagers(keystorePath, password);
+ TrustManager[] trustAllCerts = getTrustAllCerts();
+
+ SSLContext sslContext = SSLContext.getInstance(protocol);
+ sslContext.init(keyManagers, trustAllCerts, null);
+
+ return sslContext;
+ }
+
+ private static KeyManager[] getKeyManagers(String keystorePath, char[]
password) throws KeyStoreException,
+ IOException, NoSuchAlgorithmException, CertificateException,
UnrecoverableKeyException {
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS);
+ try (FileInputStream fis = new FileInputStream(keystorePath)) {
+ keyStore.load(fis, password);
+ }
+
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(keyStore, password);
+ return kmf.getKeyManagers();
+ }
+
+ public TrustManager[] getTrustAllCerts() {
+ // This is OK for testing.
+ return new TrustManager[]{
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ public void checkClientTrusted(X509Certificate[] certs,
String authType) {
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs,
String authType) {
+ }
+ }
+ };
}
}